Skip to content
3 changes: 3 additions & 0 deletions apps/solana/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@
CreatedAt time.Time

Uri string
Asset *bot.AssetNetwork

Check failure on line 54 in apps/solana/common.go

View workflow job for this annotation

GitHub Actions / lint

undefined: bot (typecheck)
PrivateKey *solana.PrivateKey

Check failure on line 55 in apps/solana/common.go

View workflow job for this annotation

GitHub Actions / lint

undefined: solana (typecheck)
}

type NonceAccount struct {
Address solana.PublicKey

Check failure on line 59 in apps/solana/common.go

View workflow job for this annotation

GitHub Actions / lint

undefined: solana (typecheck)
Hash solana.Hash

Check failure on line 60 in apps/solana/common.go

View workflow job for this annotation

GitHub Actions / lint

undefined: solana (typecheck)
}

type TokenTransfer struct {
Expand Down Expand Up @@ -414,6 +414,9 @@
}

func NonceAccountFromTx(tx *solana.Transaction) (*system.AdvanceNonceAccount, error) {
if len(tx.Message.Instructions) == 0 {
return nil, fmt.Errorf("transaction has no instructions")
}
ins := tx.Message.Instructions[0]
accounts, err := ins.ResolveInstructionAccounts(&tx.Message)
if err != nil {
Expand Down
13 changes: 11 additions & 2 deletions apps/solana/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ type AssetMetadata struct {
type Asset struct {
Address string `json:"address"`
Id string `json:"id"`
ProgramId string `json:"program_id"`
Supply string `json:"supply"`
Decimals uint32 `json:"decimals"`
IsInitialized bool `json:"is_initialized"`
MintAuthority string `json:"mint_authority"`
FreezeAuthority string `json:"freeze_authority"`
}
Expand Down Expand Up @@ -106,14 +109,17 @@ func (c *Client) RPCGetAsset(ctx context.Context, address string) (*Asset, error
if err != nil {
return nil, fmt.Errorf("solana.RPCGetAsset(%s) => %v", address, err)
}
if account == nil || account.Value == nil {
return nil, nil
}
data, err := account.Value.Data.MarshalJSON()
if err != nil {
panic(err)
return nil, fmt.Errorf("solana.RPCGetAsset(%s) marshal => %v", address, err)
}
var mint MintData
err = json.Unmarshal(data, &mint)
if err != nil {
panic(err)
return nil, fmt.Errorf("solana.RPCGetAsset(%s) unmarshal => %v", address, err)
}

mintAuthority := ""
Expand All @@ -127,7 +133,10 @@ func (c *Client) RPCGetAsset(ctx context.Context, address string) (*Asset, error
asset := &Asset{
Address: address,
Id: GenerateAssetId(address),
ProgramId: account.Value.Owner.String(),
Supply: mint.Parsed.Info.Supply,
Decimals: uint32(mint.Parsed.Info.Decimals),
IsInitialized: mint.Parsed.Info.IsInitialized,
MintAuthority: mintAuthority,
FreezeAuthority: freezeAuthority,
}
Expand Down
66 changes: 58 additions & 8 deletions apps/solana/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"github.com/shopspring/decimal"
)

const solanaInnerIndexBase = int64(1_000_000_000)

func (c *Client) CreateNonceAccount(ctx context.Context, key, nonce string, rent uint64) (*solana.Transaction, error) {
payer, err := solana.PrivateKeyFromBase58(key)
if err != nil {
Expand Down Expand Up @@ -388,12 +390,19 @@ func (c *Client) getPriorityFeeInstruction(ctx context.Context) *computebudget.I
if err != nil {
panic(err)
}
fee := getAveragePriorityFee(recentFees)
return computebudget.NewSetComputeUnitPriceInstruction(fee).Build()
}

func getAveragePriorityFee(recentFees []rpc.PriorizationFeeResult) uint64 {
if len(recentFees) == 0 {
return 1000
}
total := decimal.NewFromInt(0)
for _, fee := range recentFees {
total = total.Add(decimal.NewFromUint64(fee.PrioritizationFee))
}
fee := total.Div(decimal.NewFromInt(int64(len(recentFees)))).BigInt().Uint64()
return computebudget.NewSetComputeUnitPriceInstruction(fee).Build()
return total.Div(decimal.NewFromInt(int64(len(recentFees)))).BigInt().Uint64()
}

func ExtractTransfersFromTransaction(ctx context.Context, tx *solana.Transaction, meta *rpc.TransactionMeta, exception *solana.PublicKey) ([]*Transfer, error) {
Expand Down Expand Up @@ -440,13 +449,12 @@ func ExtractTransfersFromTransaction(ctx context.Context, tx *solana.Transaction
}

for index, ix := range msg.Instructions {
baseIndex := int64(index+1) * 10000
if transfer := extractTransfersFromInstruction(&msg, ix, tokenAccounts, owners, transfers); transfer != nil {
if exception != nil && exception.String() == transfer.Receiver {
continue
}
transfer.Signature = hash
transfer.Index = baseIndex
transfer.Index = int64(index)
transfers = append(transfers, transfer)
}

Expand All @@ -456,7 +464,7 @@ func ExtractTransfersFromTransaction(ctx context.Context, tx *solana.Transaction
continue
}
transfer.Signature = hash
transfer.Index = baseIndex + int64(innerIndex) + 1
transfer.Index = (int64(index)+1)*solanaInnerIndexBase + int64(innerIndex)
transfers = append(transfers, transfer)
}
}
Expand All @@ -472,10 +480,23 @@ func ExtractTransferFromTransactionByIndex(ctx context.Context, tx *solana.Trans
msg := tx.Message

var (
tokenAccounts = map[solana.PublicKey]token.Account{}
owners = []*solana.PublicKey{}
innerInstructions = map[uint16][]solana.CompiledInstruction{}
tokenAccounts = map[solana.PublicKey]token.Account{}
owners = []*solana.PublicKey{}
)

for _, inner := range meta.InnerInstructions {
sis := make([]solana.CompiledInstruction, len(inner.Instructions))
for idx, ii := range inner.Instructions {
sis[idx] = solana.CompiledInstruction{
ProgramIDIndex: ii.ProgramIDIndex,
Accounts: ii.Accounts,
Data: ii.Data,
}
}
innerInstructions[inner.Index] = sis
}

bs := meta.PreTokenBalances
bs = append(bs, meta.PostTokenBalances...)
for _, balance := range bs {
Expand All @@ -492,7 +513,36 @@ func ExtractTransferFromTransactionByIndex(ctx context.Context, tx *solana.Trans
}
}

return extractTransfersFromInstruction(&msg, msg.Instructions[index], tokenAccounts, owners, nil)
ix, ok := instructionByTransferIndex(&msg, innerInstructions, index)
if !ok {
return nil
}
return extractTransfersFromInstruction(&msg, ix, tokenAccounts, owners, nil)
}

func instructionByTransferIndex(msg *solana.Message, innerInstructions map[uint16][]solana.CompiledInstruction, index int64) (solana.CompiledInstruction, bool) {
if index < 0 {
return solana.CompiledInstruction{}, false
}

if index < solanaInnerIndexBase {
if index >= int64(len(msg.Instructions)) {
return solana.CompiledInstruction{}, false
}
return msg.Instructions[index], true
}

outerIndex := index/solanaInnerIndexBase - 1
if outerIndex < 0 || outerIndex >= int64(len(msg.Instructions)) {
return solana.CompiledInstruction{}, false
}

innerIndex := index % solanaInnerIndexBase
inners := innerInstructions[uint16(outerIndex)]
if innerIndex < 0 || innerIndex >= int64(len(inners)) {
return solana.CompiledInstruction{}, false
}
return inners[innerIndex], true
}

func ExtractMintsFromTransaction(tx *solana.Transaction) []string {
Expand Down
5 changes: 5 additions & 0 deletions solana/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package solana
import (
_ "embed"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
Expand Down Expand Up @@ -270,6 +271,10 @@ func (node *Node) httpLockNonce(w http.ResponseWriter, r *http.Request, params m

err = node.store.LockNonceAccountWithMix(ctx, nonce.Address, body.Mix)
if err != nil {
if errors.Is(err, store.ErrNonceAccountLimit) {
common.RenderJSON(w, r, http.StatusTooManyRequests, map[string]any{"error": "nonce limit"})
return
}
common.RenderError(w, r, err)
return
}
Expand Down
Loading
Loading