From 63ee095610cfd93c6bfae8ae8bfaf9709896af13 Mon Sep 17 00:00:00 2001 From: hundredark Date: Fri, 25 Jul 2025 17:22:26 +0800 Subject: [PATCH 1/2] use CreateIdempotent --- apps/solana/ata_CreateIdempotent.go | 321 ++++++++++++++++++++++++++++ apps/solana/common.go | 2 +- apps/solana/token2022_ata.go | 186 ---------------- apps/solana/transaction.go | 62 ++---- solana/solana_test.go | 88 ++++++++ 5 files changed, 433 insertions(+), 226 deletions(-) create mode 100644 apps/solana/ata_CreateIdempotent.go delete mode 100644 apps/solana/token2022_ata.go diff --git a/apps/solana/ata_CreateIdempotent.go b/apps/solana/ata_CreateIdempotent.go new file mode 100644 index 00000000..2e255e26 --- /dev/null +++ b/apps/solana/ata_CreateIdempotent.go @@ -0,0 +1,321 @@ +package solana + +import ( + "bytes" + "errors" + "fmt" + + ag_spew "github.com/davecgh/go-spew/spew" + ag_binary "github.com/gagliardetto/binary" + ag_treeout "github.com/gagliardetto/treeout" + + ag_solanago "github.com/gagliardetto/solana-go" + associatedtokenaccount "github.com/gagliardetto/solana-go/programs/associated-token-account" + ag_text "github.com/gagliardetto/solana-go/text" + ag_format "github.com/gagliardetto/solana-go/text/format" +) + +const ( + // Creates an associated token account for the given wallet address and + // token mint Returns an error if the account exists. + Instruction_Create uint8 = iota + + // Creates an associated token account for the given wallet address and + // token mint, if it doesn't already exist. Returns an error if the + // account exists, but with a different owner. + Instruction_CreateIdempotent +) + +type CreateIdempotent struct { + // [0] = [WRITE, SIGNER] Payer + // ··········· Funding account + // + // [1] = [WRITE] AssociatedTokenAccount + // ··········· Associated token account address to be created + // + // [2] = [] Wallet + // ··········· Wallet address for the new associated token account + // + // [3] = [] TokenMint + // ··········· The token mint for the new associated token account + // + // [4] = [] SystemProgram + // ··········· System program ID + // + // [5] = [] TokenProgram + // ··········· SPL token program ID + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (inst *CreateIdempotent) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if len(accounts) != 6 { + return fmt.Errorf("expected 6 accounts, got %v", len(accounts)) + } + inst.AccountMetaSlice = accounts + return nil +} + +func (inst CreateIdempotent) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, inst.AccountMetaSlice...) + return +} + +// NewCreateIdempotentInstructionBuilder creates a new `CreateIdempotent` instruction builder. +func NewCreateIdempotentInstructionBuilder() *CreateIdempotent { + return &CreateIdempotent{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 6), + } +} + +// SetFundingAccount sets the "funding" account. +func (inst *CreateIdempotent) SetFundingAccount(funding ag_solanago.PublicKey) *CreateIdempotent { + inst.AccountMetaSlice[0] = ag_solanago.Meta(funding).WRITE().SIGNER() + return inst +} + +// GetFundingAccount gets the "funding" account. +func (inst *CreateIdempotent) GetFundingAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetAssociatedTokenAccount sets the "associated token" account. +func (inst *CreateIdempotent) SetAssociatedTokenAccount(associatedToken ag_solanago.PublicKey) *CreateIdempotent { + inst.AccountMetaSlice[1] = ag_solanago.Meta(associatedToken).WRITE() + return inst +} + +// GetAssociatedTokenAccount gets the "associated token" account. +func (inst *CreateIdempotent) GetAssociatedTokenAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// SetWalletAccount sets the "wallet" account. +func (inst *CreateIdempotent) SetWalletAccount(wallet ag_solanago.PublicKey) *CreateIdempotent { + inst.AccountMetaSlice[2] = ag_solanago.Meta(wallet) + return inst +} + +// GetWalletAccount gets the "wallet" account. +func (inst *CreateIdempotent) GetWalletAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +// SetTokenMintAccount sets the "token mint" account. +func (inst *CreateIdempotent) SetTokenMintAccount(tokenMint ag_solanago.PublicKey) *CreateIdempotent { + inst.AccountMetaSlice[3] = ag_solanago.Meta(tokenMint) + return inst +} + +// GetTokenMintAccount gets the "token mint" account. +func (inst *CreateIdempotent) GetTokenMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[3] +} + +// SetSystemProgramAccount sets the "system program" account. +func (inst *CreateIdempotent) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *CreateIdempotent { + inst.AccountMetaSlice[4] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "system program" account. +func (inst *CreateIdempotent) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[4] +} + +// SetTokenProgramAccount sets the "token program" account. +func (inst *CreateIdempotent) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *CreateIdempotent { + inst.AccountMetaSlice[5] = ag_solanago.Meta(tokenProgram) + return inst +} + +// GetTokenProgramAccount gets the "token program" account. +func (inst *CreateIdempotent) GetTokenProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[5] +} + +// SetSysVarRentAccount sets the "sys var rent" account. +func (inst *CreateIdempotent) SetSysVarRentAccount(sysVarRent ag_solanago.PublicKey) *CreateIdempotent { + inst.AccountMetaSlice[6] = ag_solanago.Meta(sysVarRent) + return inst +} + +// GetSysVarRentAccount gets the "sys var rent" account. +func (inst *CreateIdempotent) GetSysVarRentAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[6] +} + +func (inst CreateIdempotent) Build() *AtaInstruction { + return &AtaInstruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_CreateIdempotent), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst CreateIdempotent) ValidateAndBuild() (*AtaInstruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *CreateIdempotent) Validate() error { + if inst.AccountMetaSlice.Get(0).PublicKey.IsZero() { + return errors.New("Payer not set") + } + if inst.AccountMetaSlice.Get(2).PublicKey.IsZero() { + return errors.New("Wallet not set") + } + if inst.AccountMetaSlice.Get(3).PublicKey.IsZero() { + return errors.New("Mint not set") + } + _, _, err := ag_solanago.FindAssociatedTokenAddress( + inst.AccountMetaSlice.Get(2).PublicKey, + inst.AccountMetaSlice.Get(3).PublicKey, + ) + if err != nil { + return fmt.Errorf("error while FindAssociatedTokenAddress: %w", err) + } + return nil +} + +func (inst *CreateIdempotent) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(AtaProgramName, associatedtokenaccount.ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("CreateIdempotent")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Accounts[len=6]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" payer", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(ag_format.Meta("associatedTokenAddress", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(ag_format.Meta(" wallet", inst.AccountMetaSlice.Get(2))) + accountsBranch.Child(ag_format.Meta(" tokenMint", inst.AccountMetaSlice.Get(3))) + accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice.Get(4))) + accountsBranch.Child(ag_format.Meta(" tokenProgram", inst.AccountMetaSlice.Get(5))) + }) + }) + }) +} + +func (inst CreateIdempotent) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} + +func (inst *CreateIdempotent) UnmarshalWithDecoder(_ *ag_binary.Decoder) (err error) { + return nil +} + +// NewCreateIdempotentInstruction declares a new CreateIdempotent instruction with the provided parameters and accounts. +func NewCreateIdempotentInstruction( + // Accounts: + funding ag_solanago.PublicKey, + associatedTokenAccount ag_solanago.PublicKey, + wallet ag_solanago.PublicKey, + tokenMint ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey, + tokenProgram ag_solanago.PublicKey, +) *CreateIdempotent { + return NewCreateIdempotentInstructionBuilder(). + SetFundingAccount(funding). + SetAssociatedTokenAccount(associatedTokenAccount). + SetWalletAccount(wallet). + SetTokenMintAccount(tokenMint). + SetSystemProgramAccount(systemProgram). + SetTokenProgramAccount(tokenProgram) +} + +var ProgramID ag_solanago.PublicKey = ag_solanago.SPLAssociatedTokenAccountProgramID + +// func SetProgramID(pubkey ag_solanago.PublicKey) { +// ProgramID = pubkey +// ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeAtaInstruction) +// } + +const AtaProgramName = "AssociatedTokenAccount" + +// func init() { +// ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeAtaInstruction) +// } + +// InstructionIDToName returns the name of the instruction given its ID. +func InstructionIDToName(id uint8) string { + switch id { + case Instruction_Create: + return "Create" + case Instruction_CreateIdempotent: + return "CreateIdempotent" + default: + return "" + } +} + +type AtaInstruction struct { + ag_binary.BaseVariant +} + +func (inst *AtaInstruction) EncodeToTree(parent ag_treeout.Branches) { + if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok { + enToTree.EncodeToTree(parent) + } else { + parent.Child(ag_spew.Sdump(inst)) + } +} + +var AtaInstructionImplDef = ag_binary.NewVariantDefinition( + ag_binary.Uint8TypeIDEncoding, + []ag_binary.VariantType{ + { + Name: "Create", Type: (*associatedtokenaccount.Create)(nil), + }, + { + Name: "CreateIdempotent", Type: (*CreateIdempotent)(nil), + }, + }, +) + +func (inst *AtaInstruction) ProgramID() ag_solanago.PublicKey { + return ProgramID +} + +func (inst *AtaInstruction) Accounts() (out []*ag_solanago.AccountMeta) { + return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts() +} + +func (inst *AtaInstruction) Data() ([]byte, error) { + buf := new(bytes.Buffer) + if err := ag_binary.NewBinEncoder(buf).Encode(inst); err != nil { + return nil, fmt.Errorf("unable to encode instruction: %w", err) + } + return buf.Bytes(), nil +} + +func (inst *AtaInstruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error { + return encoder.Encode(inst.Impl, option) +} + +func (inst *AtaInstruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + return inst.BaseVariant.UnmarshalBinaryVariant(decoder, AtaInstructionImplDef) +} + +func (inst AtaInstruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + err := encoder.WriteUint8(inst.TypeID.Uint8()) + if err != nil { + return fmt.Errorf("unable to write variant type: %w", err) + } + return encoder.Encode(inst.Impl) +} + +func DecodeAtaInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*AtaInstruction, error) { + inst := new(AtaInstruction) + if err := ag_binary.NewBinDecoder(data).Decode(inst); err != nil { + return nil, fmt.Errorf("unable to decode instruction: %w", err) + } + if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok { + err := v.SetAccounts(accounts) + if err != nil { + return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) + } + } + return inst, nil +} diff --git a/apps/solana/common.go b/apps/solana/common.go index ec51e033..a1e1bee8 100644 --- a/apps/solana/common.go +++ b/apps/solana/common.go @@ -233,7 +233,7 @@ func ExtractCreatedAtasFromTransaction(ctx context.Context, tx *solana.Transacti ata := a.GetAccounts()[1] as = append(as, ata.PublicKey) } - if a, ok := ix.Impl.(*Create); ok { + if a, ok := ix.Impl.(*CreateIdempotent); ok { ata := a.GetAccounts()[1] as = append(as, ata.PublicKey) } diff --git a/apps/solana/token2022_ata.go b/apps/solana/token2022_ata.go deleted file mode 100644 index 058b4760..00000000 --- a/apps/solana/token2022_ata.go +++ /dev/null @@ -1,186 +0,0 @@ -package solana - -import ( - "errors" - - bin "github.com/gagliardetto/binary" - "github.com/gagliardetto/solana-go" - tokenAta "github.com/gagliardetto/solana-go/programs/associated-token-account" - format "github.com/gagliardetto/solana-go/text/format" - treeout "github.com/gagliardetto/treeout" -) - -type Create struct { - Payer solana.PublicKey `bin:"-" borsh_skip:"true"` - Wallet solana.PublicKey `bin:"-" borsh_skip:"true"` - Mint solana.PublicKey `bin:"-" borsh_skip:"true"` - - // [0] = [WRITE, SIGNER] Payer - // ··········· Funding account - // - // [1] = [WRITE] AssociatedTokenAccount - // ··········· Associated token account address to be created - // - // [2] = [] Wallet - // ··········· Wallet address for the new associated token account - // - // [3] = [] TokenMint - // ··········· The token mint for the new associated token account - // - // [4] = [] SystemProgram - // ··········· System program ID - // - // [5] = [] TokenProgram - // ··········· SPL token program ID - // - // [6] = [] SysVarRent - // ··········· SysVarRentPubkey - solana.AccountMetaSlice `bin:"-" borsh_skip:"true"` -} - -// NewCreateInstructionBuilder creates a new `Create` instruction builder. -func NewCreateInstructionBuilder() *Create { - nd := &Create{} - return nd -} - -func (inst *Create) SetPayer(payer solana.PublicKey) *Create { - inst.Payer = payer - return inst -} - -func (inst *Create) SetWallet(wallet solana.PublicKey) *Create { - inst.Wallet = wallet - return inst -} - -func (inst *Create) SetMint(mint solana.PublicKey) *Create { - inst.Mint = mint - return inst -} - -func (inst Create) Build() *tokenAta.Instruction { - // Find the associatedTokenAddress; - associatedTokenAddress := FindAssociatedTokenAddress( - inst.Wallet, - inst.Mint, - solana.Token2022ProgramID, - ) - - keys := []*solana.AccountMeta{ - { - PublicKey: inst.Payer, - IsSigner: true, - IsWritable: true, - }, - { - PublicKey: associatedTokenAddress, - IsSigner: false, - IsWritable: true, - }, - { - PublicKey: inst.Wallet, - IsSigner: false, - IsWritable: false, - }, - { - PublicKey: inst.Mint, - IsSigner: false, - IsWritable: false, - }, - { - PublicKey: solana.SystemProgramID, - IsSigner: false, - IsWritable: false, - }, - { - PublicKey: solana.Token2022ProgramID, // origin: solana.TokenProgramID - IsSigner: false, - IsWritable: false, - }, - { - PublicKey: solana.SysVarRentPubkey, - IsSigner: false, - IsWritable: false, - }, - } - - inst.AccountMetaSlice = keys - - return &tokenAta.Instruction{BaseVariant: bin.BaseVariant{ - Impl: inst, - TypeID: bin.NoTypeIDDefaultID, - }} -} - -// ValidateAndBuild validates the instruction accounts. -// If there is a validation error, return the error. -// Otherwise, build and return the instruction. -func (inst Create) ValidateAndBuild() (*tokenAta.Instruction, error) { - if err := inst.Validate(); err != nil { - return nil, err - } - return inst.Build(), nil -} - -func (inst *Create) Validate() error { - if inst.Payer.IsZero() { - return errors.New("payer not set") - } - if inst.Wallet.IsZero() { - return errors.New("wallet not set") - } - if inst.Mint.IsZero() { - return errors.New("mint not set") - } - _ = FindAssociatedTokenAddress( - inst.Wallet, - inst.Mint, - solana.Token2022ProgramID, - ) - return nil -} - -func (inst *Create) EncodeToTree(parent treeout.Branches) { - parent.Child(format.Program(tokenAta.ProgramName, tokenAta.ProgramID)). - // - ParentFunc(func(programBranch treeout.Branches) { - programBranch.Child(format.Instruction("Create")). - // - ParentFunc(func(instructionBranch treeout.Branches) { - - // Parameters of the instruction: - instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch treeout.Branches) {}) - - // Accounts of the instruction: - instructionBranch.Child("Accounts[len=7").ParentFunc(func(accountsBranch treeout.Branches) { - accountsBranch.Child(format.Meta(" payer", inst.AccountMetaSlice.Get(0))) - accountsBranch.Child(format.Meta("associatedTokenAddress", inst.AccountMetaSlice.Get(1))) - accountsBranch.Child(format.Meta(" wallet", inst.AccountMetaSlice.Get(2))) - accountsBranch.Child(format.Meta(" tokenMint", inst.AccountMetaSlice.Get(3))) - accountsBranch.Child(format.Meta(" systemProgram", inst.AccountMetaSlice.Get(4))) - accountsBranch.Child(format.Meta(" tokenProgram", inst.AccountMetaSlice.Get(5))) - accountsBranch.Child(format.Meta(" sysVarRent", inst.AccountMetaSlice.Get(6))) - }) - }) - }) -} - -func (inst Create) MarshalWithEncoder(encoder *bin.Encoder) error { - return encoder.WriteBytes([]byte{}, false) -} - -func (inst *Create) UnmarshalWithDecoder(decoder *bin.Decoder) error { - return nil -} - -func NewAta2022CreateInstruction( - payer solana.PublicKey, - walletAddress solana.PublicKey, - splTokenMintAddress solana.PublicKey, -) *Create { - return NewCreateInstructionBuilder(). - SetPayer(payer). - SetWallet(walletAddress). - SetMint(splTokenMintAddress) -} diff --git a/apps/solana/transaction.go b/apps/solana/transaction.go index 995ad7c0..bb88b238 100644 --- a/apps/solana/transaction.go +++ b/apps/solana/transaction.go @@ -8,7 +8,6 @@ import ( "github.com/MixinNetwork/safe/common" "github.com/gagliardetto/solana-go" - tokenAta "github.com/gagliardetto/solana-go/programs/associated-token-account" computebudget "github.com/gagliardetto/solana-go/programs/compute-budget" "github.com/gagliardetto/solana-go/programs/system" "github.com/gagliardetto/solana-go/programs/token" @@ -199,7 +198,7 @@ func (c *Client) TransferOrMintTokens(ctx context.Context, payer, mtg solana.Pub for _, transfer := range transfers { if transfer.SolanaAsset { - b, err := c.addTransferSolanaAssetInstruction(ctx, builder, transfer, payer, mtg) + b, err := c.AddTransferSolanaAssetInstruction(ctx, builder, transfer, payer, mtg) if err != nil { return nil, err } @@ -209,19 +208,16 @@ func (c *Client) TransferOrMintTokens(ctx context.Context, payer, mtg solana.Pub mint := transfer.Mint ataAddress := FindAssociatedTokenAddress(transfer.Destination, mint, solana.TokenProgramID) - ata, err := c.RPCGetAccount(ctx, ataAddress) - if err != nil { - return nil, err - } - if ata == nil || common.CheckTestEnvironment(ctx) { - builder.AddInstruction( - tokenAta.NewCreateInstruction( - payer, - transfer.Destination, - mint, - ).Build(), - ) - } + builder.AddInstruction( + NewCreateIdempotentInstruction( + payer, + ataAddress, + transfer.Destination, + mint, + system.ProgramID, + solana.TokenProgramID, + ).Build(), + ) builder.AddInstruction( token.NewMintToInstruction( @@ -246,7 +242,7 @@ func (c *Client) TransferOrBurnTokens(ctx context.Context, payer, user solana.Pu for _, transfer := range transfers { if transfer.SolanaAsset { - b, err := c.addTransferSolanaAssetInstruction(ctx, builder, transfer, payer, user) + b, err := c.AddTransferSolanaAssetInstruction(ctx, builder, transfer, payer, user) if err != nil { return nil, err } @@ -270,7 +266,7 @@ func (c *Client) TransferOrBurnTokens(ctx context.Context, payer, user solana.Pu return builder.Build() } -func (c *Client) addTransferSolanaAssetInstruction(ctx context.Context, builder *solana.TransactionBuilder, transfer *TokenTransfer, payer, source solana.PublicKey) (*solana.TransactionBuilder, error) { +func (c *Client) AddTransferSolanaAssetInstruction(ctx context.Context, builder *solana.TransactionBuilder, transfer *TokenTransfer, payer, source solana.PublicKey) (*solana.TransactionBuilder, error) { if !transfer.SolanaAsset { panic(transfer.AssetId) } @@ -297,22 +293,19 @@ func (c *Client) addTransferSolanaAssetInstruction(ctx context.Context, builder src := FindAssociatedTokenAddress(source, transfer.Mint, tokenProgram) dst := FindAssociatedTokenAddress(transfer.Destination, transfer.Mint, tokenProgram) - ata, err := c.RPCGetAccount(ctx, dst) - if err != nil { - return nil, err - } + builder.AddInstruction( + NewCreateIdempotentInstruction( + payer, + dst, + transfer.Destination, + transfer.Mint, + system.ProgramID, + tokenProgram, + ).Build(), + ) switch { case tokenProgram.Equals(solana.TokenProgramID): - if ata == nil || common.CheckTestEnvironment(ctx) { - builder.AddInstruction( - tokenAta.NewCreateInstruction( - payer, - transfer.Destination, - transfer.Mint, - ).Build(), - ) - } builder.AddInstruction( token.NewTransferCheckedInstruction( transfer.Amount, @@ -325,15 +318,6 @@ func (c *Client) addTransferSolanaAssetInstruction(ctx context.Context, builder ).Build(), ) case tokenProgram.Equals(solana.Token2022ProgramID): - if ata == nil || common.CheckTestEnvironment(ctx) { - builder.AddInstruction( - NewAta2022CreateInstruction( - payer, - transfer.Destination, - transfer.Mint, - ).Build(), - ) - } builder.AddInstruction( NewToken2022TransferCheckedInstruction( transfer.Amount, diff --git a/solana/solana_test.go b/solana/solana_test.go index c38b7ad6..f54fbec0 100644 --- a/solana/solana_test.go +++ b/solana/solana_test.go @@ -129,6 +129,94 @@ func TestCreateV1(t *testing.T) { ) } +func TestCreateIdempotent(t *testing.T) { + require := require.New(t) + ctx := context.Background() + rpc1 := testRpcEndpoint + c := solanaApp.NewClient(rpc1) + + blockhash := solana.MustHashFromBase58("2fGgNDhkTwhBH1PqS6xpgmKSCSVdYUGuZBMCSQoAMRDt") + owner := solana.MPK("73yoz7kK3zgh2ScD9aTJpXCrKHETi1xyEKfMTH95ugff") + mint := solana.MPK("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB") + ata := solanaApp.FindAssociatedTokenAddress(owner, mint, solana.TokenProgramID) + tx, err := solana.NewTransaction( + []solana.Instruction{ + solanaApp.NewCreateIdempotentInstruction( + owner, + ata, + owner, + mint, + system.ProgramID, + solana.TokenProgramID, + ).Build(), + }, + blockhash, + solana.TransactionPayer(owner), + ) + require.Nil(err) + + data, err := tx.Message.MarshalBinary() + require.Nil(err) + require.Equal( + "0100040659e984bf1923c33a3d247aa4f9ce780423028db7b8d0e1ef452d7b3d46dd8f9ec3f3b4b766373251dbbdde0c9be5824640f5ede70bbeda2422c57da2d9078380ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e208264000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a98c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f85918a97c54168e4c731e1cb11427eac8ad346847dbc1714cf592b2f748613a52fb0105060001000203040101", + hex.EncodeToString(data), + ) + + blockhash = solana.MustHashFromBase58("AqLCRjEWfstqpdzyvLbn3NKZeW9oXRmuaKxebBvTQdK7") + mint = solana.MPK("9wA7eF6kaBkPhCbwDu8gojpHSfdjTMWD8VhbmS846nN7") + ata = solanaApp.FindAssociatedTokenAddress(owner, mint, solana.Token2022ProgramID) + tx, err = solana.NewTransaction( + []solana.Instruction{ + solanaApp.NewCreateIdempotentInstruction( + owner, + ata, + owner, + mint, + system.ProgramID, + solana.Token2022ProgramID, + ).Build(), + }, + blockhash, + solana.TransactionPayer(owner), + ) + require.Nil(err) + + data, err = tx.Message.MarshalBinary() + require.Nil(err) + require.Equal( + "0100040659e984bf1923c33a3d247aa4f9ce780423028db7b8d0e1ef452d7b3d46dd8f9e667c6ecde852cd23d501dd593eb1b5e115b0285491058be7682fb475e797283e84bd2a383f1dfc9e5ed5982192551ab563b0057f26b74e581c04e953810119b4000000000000000000000000000000000000000000000000000000000000000006ddf6e1ee758fde18425dbce46ccddab61afc4d83b90d27febdf928d8a18bfc8c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859921ac46d8d98f055dc016163184fa4112145e4aa2c53b892dcc433fa713d13020105060001000203040101", + hex.EncodeToString(data), + ) + + builder := solana.NewTransactionBuilder() + builder.SetFeePayer(owner) + transfer := &solanaApp.TokenTransfer{ + SolanaAsset: true, + AssetId: "cb54aed4-1893-3977-b739-ec7b2e04f0c5", + Destination: solana.MPK("9WKkVWoWuj8RQbSbe6tsU939aJYbjsDEEPb9J7p98bcQ"), + Mint: solana.MPK("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"), + Amount: 1, + Decimals: 6, + } + builder, err = c.AddTransferSolanaAssetInstruction(ctx, builder, transfer, owner, owner) + require.Nil(err) + + builder.SetRecentBlockHash(solana.MustHashFromBase58("6QfjA7T1GojSuoo2NVd38iw47qBtgVN4kDsTMyp4QxFs")) + tx, err = builder.Build() + require.Nil(err) + data, err = tx.Message.MarshalBinary() + require.Nil(err) + require.Equal( + "0100050859e984bf1923c33a3d247aa4f9ce780423028db7b8d0e1ef452d7b3d46dd8f9e19d947119f4554e9755871e1bb887205dfedb6262ef40c15abce81d3530cc787c3f3b4b766373251dbbdde0c9be5824640f5ede70bbeda2422c57da2d90783807e608a165388caafffe2a5d24baa9589c2ecfc10b674b9adff936b94e90e42c5ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e208264000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a98c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859505a94f280514a58cb479c399a6cb9006c3b7337eb6b5731015687bb0f14454202070600010304050601010604020401000a0c010000000000000006", + hex.EncodeToString(data), + ) + + ata = solanaApp.FindAssociatedTokenAddress(transfer.Destination, transfer.Mint, solana.TokenProgramID) + as := solanaApp.ExtractCreatedAtasFromTransaction(ctx, tx) + require.Len(as, 1) + require.True(as[0].Equals(ata)) +} + func testFROSTSign(ctx context.Context, require *require.Assertions, nodes []*Node, nonce, public string, tx *solana.Transaction) { msg, err := tx.Message.MarshalBinary() require.Nil(err) From 732da14d73965f911e297d0eaf478fdd8eabf02b Mon Sep 17 00:00:00 2001 From: hundredark Date: Fri, 25 Jul 2025 17:32:47 +0800 Subject: [PATCH 2/2] fix test --- solana/test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solana/test.go b/solana/test.go index 71fcb169..ef53e3f1 100644 --- a/solana/test.go +++ b/solana/test.go @@ -188,7 +188,7 @@ func (n *testNetwork) msgChannel(id party.ID) chan []byte { func getTestSystemConfirmCallMessage(signature string) string { if signature == "2tPHv7kbUeHRWHgVKKddQqXnjDhuX84kTyCvRy1BmCM4m4Fkq4vJmNAz8A7fXqckrSNRTAKuPmAPWnzr5T7eCChb" { - return "4d57022c484aebdb7d4472c16740f7e8c4f9047b41cbcf05a9d517558bc276c7" + return "746a738719b9d3f6cf59001820553e77fa629a2bbcd3cdf88b97cc919d400178" } if signature == "42fwVqHYmfLqoqQ3XgELu72FL6t2Q2HCqY7XzkVCdVsWHGoT6DHBk7qzoUkpfjqs42ygSSnFWzarQZdpUX9tLK6r" { return "03bd9b9ebe4a619f52eb0fcb81c647eb81ee18b8728ffa6a6cc7b6a04f03540d"