From 8bbeef64210ff5b5df9e1a168167e363197a690c Mon Sep 17 00:00:00 2001 From: Ellenp2p Date: Thu, 13 Mar 2025 01:57:51 +0800 Subject: [PATCH 01/11] Add SimulateMultiTransaction method and AccountAuthenticatorNoAccountAuthenticator implementation --- client.go | 4 + ...ountAuthenticatorNoAccountAuthenticator.go | 45 +++++ crypto/authenticator.go | 2 + examples/simulate_mult_transaction/main.go | 162 ++++++++++++++++++ nodeClient.go | 70 ++++++++ rawTransaction.go | 1 + 6 files changed, 284 insertions(+) create mode 100644 crypto/AccountAuthenticatorNoAccountAuthenticator.go create mode 100644 examples/simulate_mult_transaction/main.go diff --git a/client.go b/client.go index 8aa35b0..65b9e60 100644 --- a/client.go +++ b/client.go @@ -749,6 +749,10 @@ func (client *Client) SimulateTransaction(rawTxn *RawTransaction, sender Transac return client.nodeClient.SimulateTransaction(rawTxn, sender, options...) } +func (client *Client) SimulateMultiTransaction(rawTxnWithData *RawTransactionWithData, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error) { + return client.nodeClient.SimulateMultiTransaction(rawTxnWithData, sender, options...) +} + // GetChainId Retrieves the ChainId of the network // Note this will be cached forever, or taken directly from the config func (client *Client) GetChainId() (chainId uint8, err error) { diff --git a/crypto/AccountAuthenticatorNoAccountAuthenticator.go b/crypto/AccountAuthenticatorNoAccountAuthenticator.go new file mode 100644 index 0000000..a5c2dbe --- /dev/null +++ b/crypto/AccountAuthenticatorNoAccountAuthenticator.go @@ -0,0 +1,45 @@ +package crypto + +import "github.com/aptos-labs/aptos-go-sdk/bcs" + +type AccountAuthenticatorNoAccountAuthenticator struct { +} + +func (aa *AccountAuthenticatorNoAccountAuthenticator) UnmarshalBCS(des *bcs.Deserializer) { + +} + +func (aa *AccountAuthenticatorNoAccountAuthenticator) MarshalBCS(ser *bcs.Serializer) { +} + +func (aa *AccountAuthenticatorNoAccountAuthenticator) PublicKey() PublicKey { + var publicKey PublicKey + err := (publicKey).FromHex("0x0000000000000000000000000000000000000000000000000000000000000000") + println(publicKey.ToHex()) + if err != nil { + + // Handle error or log it + // For this case, it should never fail since we're using a valid zero key + } + + return publicKey +} + +// Signature returns the signature of the authenticator +// +// Implements: +// - [AccountAuthenticatorImpl] +func (ea *AccountAuthenticatorNoAccountAuthenticator) Signature() Signature { + var signature Signature + (signature).FromHex("0x0000000000000000000000000000000000000000000000000000000000000000") + println(signature.ToHex()) + return signature +} + +// Verify verifies the signature against the message +// +// Implements: +// - [AccountAuthenticatorImpl] +func (aa *AccountAuthenticatorNoAccountAuthenticator) Verify(msg []byte) bool { + return false +} \ No newline at end of file diff --git a/crypto/authenticator.go b/crypto/authenticator.go index a03bb8a..42a7e62 100644 --- a/crypto/authenticator.go +++ b/crypto/authenticator.go @@ -3,6 +3,7 @@ package crypto import ( "errors" "fmt" + "github.com/aptos-labs/aptos-go-sdk/bcs" ) @@ -38,6 +39,7 @@ const ( AccountAuthenticatorMultiEd25519 AccountAuthenticatorType = 1 // AccountAuthenticatorMultiEd25519 is the authenticator type for multi-ed25519 accounts AccountAuthenticatorSingleSender AccountAuthenticatorType = 2 // AccountAuthenticatorSingleSender is the authenticator type for single-key accounts AccountAuthenticatorMultiKey AccountAuthenticatorType = 3 // AccountAuthenticatorMultiKey is the authenticator type for multi-key accounts + AccountAuthenticatorNoAccount AccountAuthenticatorType = 4 ) // AccountAuthenticator a generic authenticator type for a transaction diff --git a/examples/simulate_mult_transaction/main.go b/examples/simulate_mult_transaction/main.go new file mode 100644 index 0000000..2d0dbd2 --- /dev/null +++ b/examples/simulate_mult_transaction/main.go @@ -0,0 +1,162 @@ +package main + +import ( + "fmt" + + "github.com/aptos-labs/aptos-go-sdk" + "github.com/aptos-labs/aptos-go-sdk/bcs" + "github.com/aptos-labs/aptos-go-sdk/crypto" +) + +const MultiagentScript = "0xa11ceb0b0700000a0601000403040d04110405151b07302f085f2000000001010203040001000306020100010105010704060c060c03030205050001060c010501090003060c05030109010d6170746f735f6163636f756e74067369676e65720a616464726573735f6f660e7472616e736665725f636f696e73000000000000000000000000000000000000000000000000000000000000000102000000010f0a0011000c040a0111000c050b000b050b0238000b010b040b03380102" + +const FundAmount = 100_000_000 +const TransferAmount uint64 = 1 + +// example This example shows you how to make an APT transfer transaction in the simplest possible way +func example(networkConfig aptos.NetworkConfig) { + // Create a client for Aptos + client, err := aptos.NewClient(networkConfig) + if err != nil { + panic("Failed to create client:" + err.Error()) + } + + // Create accounts locally for alice and bob + + alice, err := aptos.NewEd25519Account() + if err != nil { + panic("Failed to create sender:" + err.Error()) + } + + bob, err := aptos.NewEd25519Account() + if err != nil { + panic("Failed to create sender:" + err.Error()) + } + + fmt.Printf("\n=== Addresses ===\n") + fmt.Printf("Alice: %s\n", alice.Address.String()) + fmt.Printf("Bob:%s\n", bob.Address.String()) + + // Fund the sender with the faucet to create it on-chain + err = client.Fund(alice.Address, FundAmount) + if err != nil { + panic("Failed to fund alice:" + err.Error()) + } + err = client.Fund(bob.Address, FundAmount) + if err != nil { + panic("Failed to fund bob:" + err.Error()) + } + aliceBalance, err := client.AccountAPTBalance(alice.Address) + if err != nil { + panic("Failed to retrieve alice balance:" + err.Error()) + } + bobBalance, err := client.AccountAPTBalance(bob.Address) + if err != nil { + panic("Failed to retrieve bob balance:" + err.Error()) + } + fmt.Printf("\n=== Initial Balances ===\n") + fmt.Printf("Alice: %d\n", aliceBalance) + fmt.Printf("Bob:%d\n", bobBalance) + + // 1. Build transaction + // script, err := util.ParseHex(MultiagentScript) + // if err != nil { + // panic("Failed to deserialize script:" + err.Error()) + // } + + serializer := &bcs.Serializer{} + + bob.Address.MarshalBCS(serializer) + accountBytes := serializer.ToBytes() + + if err != nil { + panic("Failed to serialize alice's address:" + err.Error()) + } + + serializer = &bcs.Serializer{} + + serializer.U64(TransferAmount) + amountBytes := serializer.ToBytes() + + if err != nil { + panic("Failed to serialize transfer amount:" + err.Error()) + } + + rawTxn, err := client.BuildTransactionMultiAgent(alice.AccountAddress(), aptos.TransactionPayload{ + Payload: &aptos.EntryFunction{ + Module: aptos.ModuleId{ + Address: aptos.AccountOne, + Name: "aptos_account", + }, + Function: "transfer", + ArgTypes: []aptos.TypeTag{}, + Args: [][]byte{ + accountBytes, + amountBytes, + }, + }}, aptos.FeePayer(&bob.Address)) + if err != nil { + panic("Failed to build multiagent raw transaction:" + err.Error()) + } + + // 2. Simulate transaction (optional) + // This is useful for understanding how much the transaction will cost + // and to ensure that the transaction is valid before sending it to the network + // This is optional, but recommended + // TODO: Support simulate transaction with multi-agent / fee payer + simulationResult, err := client.SimulateMultiTransaction(rawTxn, alice) + if err != nil { + panic("Failed to simulate transaction:" + err.Error()) + } + fmt.Printf("\n=== Simulation ===\n") + fmt.Printf("Gas unit price: %d\n", simulationResult[0].GasUnitPrice) + fmt.Printf("Gas used: %d\n", simulationResult[0].GasUsed) + fmt.Printf("Total gas fee: %d\n", simulationResult[0].GasUsed*simulationResult[0].GasUnitPrice) + fmt.Printf("Status: %s\n", simulationResult[0].VmStatus) + + // 3. Sign transaction with both parties separately, this would be on different machines or places + aliceAuth, err := rawTxn.Sign(alice) + if err != nil { + panic("Failed to sign multiagent transaction with alice:" + err.Error()) + } + bobAuth, err := rawTxn.Sign(bob) + if err != nil { + panic("Failed to sign multiagent transaction with bob:" + err.Error()) + } + + // 3.a. merge the signatures together into a single transaction + signedTxn, ok := rawTxn.ToMultiAgentSignedTransaction(aliceAuth, []crypto.AccountAuthenticator{*bobAuth}) + if !ok { + panic("Failed to build a signed multiagent transaction") + } + + // 4. Submit transaction + submitResult, err := client.SubmitTransaction(signedTxn) + if err != nil { + panic("Failed to submit transaction:" + err.Error()) + } + txnHash := submitResult.Hash + + // 5. Wait for the transaction to complete + _, err = client.WaitForTransaction(txnHash) + if err != nil { + panic("Failed to wait for transaction:" + err.Error()) + } + + // Check balances + aliceBalance, err = client.AccountAPTBalance(alice.Address) + if err != nil { + panic("Failed to retrieve alice balance:" + err.Error()) + } + bobBalance, err = client.AccountAPTBalance(bob.Address) + if err != nil { + panic("Failed to retrieve bob balance:" + err.Error()) + } + fmt.Printf("\n=== Intermediate Balances ===\n") + fmt.Printf("Alice: %d\n", aliceBalance) + fmt.Printf("Bob:%d\n", bobBalance) +} + +func main() { + example(aptos.DevnetConfig) +} diff --git a/nodeClient.go b/nodeClient.go index 7f67563..a2673b3 100644 --- a/nodeClient.go +++ b/nodeClient.go @@ -730,6 +730,76 @@ func (rc *NodeClient) SimulateTransaction(rawTxn *RawTransaction, sender Transac return data, nil } +// SimulateMultiTransaction simulates a transaction with optional fee payer and secondary signers +// If feePayerAddress is nil, no fee payer will be used +// If secondarySignerAddresses is nil or empty, no secondary signers will be used +func (rc *NodeClient) SimulateMultiTransaction(rawTxnWithData *RawTransactionWithData, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error) { + if( rawTxnWithData == nil ) { + return nil, fmt.Errorf("rawTxnWithData is nil") + } + switch rawTxnWithData.Variant{ + case MultiAgentWithFeePayerRawTransactionWithDataVariant: + signedFeePayerTxn, ok := rawTxnWithData.ToFeePayerSignedTransaction( + sender.SimulationAuthenticator(), + &crypto.AccountAuthenticator{ + Variant: crypto.AccountAuthenticatorNoAccount, + Auth: &crypto.AccountAuthenticatorNoAccountAuthenticator{}, + }, + []crypto.AccountAuthenticator{}, + ); + if !ok { + return nil, fmt.Errorf("failed to sign agent transaction") + } + return rc.simulateTransactionWithSignedTxn(signedFeePayerTxn, options...) + case MultiAgentRawTransactionWithDataVariant: + signedAgentTxn, ok := rawTxnWithData.ToMultiAgentSignedTransaction( + sender.SimulationAuthenticator(), + []crypto.AccountAuthenticator{}, + ) + if !ok { + return nil, fmt.Errorf("failed to sign agent transaction") + } + return rc.simulateTransactionWithSignedTxn(signedAgentTxn, options...) + default: + return nil, fmt.Errorf("unsupported raw transaction with data variant %v", rawTxnWithData.Variant) + } +} + +// Helper method to avoid code duplication +func (rc *NodeClient) simulateTransactionWithSignedTxn(signedTxn *SignedTransaction, options ...any) ([]*api.UserTransaction, error) { + sblob, err := bcs.Serialize(signedTxn) + if err != nil { + return nil, err + } + bodyReader := bytes.NewReader(sblob) + au := rc.baseUrl.JoinPath("transactions/simulate") + + // parse simulate tx options + params := url.Values{} + for i, arg := range options { + switch value := arg.(type) { + case EstimateGasUnitPrice: + params.Set("estimate_gas_unit_price", strconv.FormatBool(bool(value))) + case EstimateMaxGasAmount: + params.Set("estimate_max_gas_amount", strconv.FormatBool(bool(value))) + case EstimatePrioritizedGasUnitPrice: + params.Set("estimate_prioritized_gas_unit_price", strconv.FormatBool(bool(value))) + default: + return nil, fmt.Errorf("SimulateTransaction arg %d bad type %T", i+1, arg) + } + } + if len(params) != 0 { + au.RawQuery = params.Encode() + } + + data, err := Post[[]*api.UserTransaction](rc, au.String(), ContentTypeAptosSignedTxnBcs, bodyReader) + if err != nil { + return nil, fmt.Errorf("simulate transaction api err: %w", err) + } + + return data, nil +} + // GetChainId gets the chain ID of the network func (rc *NodeClient) GetChainId() (chainId uint8, err error) { if rc.chainId == 0 { diff --git a/rawTransaction.go b/rawTransaction.go index 0fd7a20..6d03313 100644 --- a/rawTransaction.go +++ b/rawTransaction.go @@ -2,6 +2,7 @@ package aptos import ( "fmt" + "github.com/aptos-labs/aptos-go-sdk/bcs" "github.com/aptos-labs/aptos-go-sdk/crypto" "golang.org/x/crypto/sha3" From 6e41f6389162512aedc65c04c60a68c04236934c Mon Sep 17 00:00:00 2001 From: Ellenp2p Date: Thu, 13 Mar 2025 15:00:32 +0800 Subject: [PATCH 02/11] Enhance SimulateMultiTransaction method to support additional signers and update related documentation --- client.go | 45 +++++++++++++++++++++- examples/simulate_mult_transaction/main.go | 13 +++---- nodeClient.go | 16 ++++---- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/client.go b/client.go index 65b9e60..6252ef6 100644 --- a/client.go +++ b/client.go @@ -6,6 +6,7 @@ import ( "time" "github.com/aptos-labs/aptos-go-sdk/api" + "github.com/aptos-labs/aptos-go-sdk/crypto" "github.com/hasura/go-graphql-client" ) @@ -749,8 +750,48 @@ func (client *Client) SimulateTransaction(rawTxn *RawTransaction, sender Transac return client.nodeClient.SimulateTransaction(rawTxn, sender, options...) } -func (client *Client) SimulateMultiTransaction(rawTxnWithData *RawTransactionWithData, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error) { - return client.nodeClient.SimulateMultiTransaction(rawTxnWithData, sender, options...) +// SimulateMultiTransaction simulates a multi-transaction on the Aptos blockchain. +// It takes a raw transaction with data, a transaction signer, and optional parameters. +// It returns a slice of UserTransaction and an error if the simulation fails. +// +// Parameters: +// - rawTxnWithData: A pointer to RawTransactionWithData containing the transaction details. +// - sender: A TransactionSigner that signs the transaction. +// - options: Optional parameters for the simulation. +// +// Returns: +// - data: A slice of UserTransaction containing the simulated transaction results. +// - err: An error if the simulation fails. +// SimulateMultiTransaction simulates a multi-signature transaction without broadcasting it to the network. +// This function takes a raw transaction with data, a sender transaction signer, and additional signers. +// It returns the simulated user transactions or an error if the simulation fails. +// +// Parameters: +// - rawTxnWithData: A pointer to the raw transaction data to be simulated. +// - sender: The primary transaction signer. +// - additionalSigners: A slice of additional signers' account authenticators. +// - options: Additional options for the simulation. +// +// Returns: +// - data: A slice of pointers to the simulated user transactions. +// - err: An error if the simulation fails. +func (client *Client) SimulateMultiTransaction(rawTxnWithData *RawTransactionWithData, sender TransactionSigner, additionalSigners []crypto.AccountAuthenticator, options ...any) (data []*api.UserTransaction, err error) { + return client.nodeClient.SimulateMultiTransaction(rawTxnWithData, sender, additionalSigners , options...) +} + +// SimulateTransactionWithSignedTxn simulates a transaction using a signed transaction. +// This function sends the signed transaction to the node client for simulation and returns +// the resulting user transactions and any error encountered during the simulation. +// +// Parameters: +// - signedTxn: A pointer to the SignedTransaction struct representing the signed transaction to be simulated. +// - options: Additional optional parameters that can be passed to the simulation. +// +// Returns: +// - data: A slice of pointers to api.UserTransaction structs representing the simulated user transactions. +// - err: An error object if an error occurred during the simulation, otherwise nil. +func (client *Client) SimulateTransactionWithSignedTxn(signedTxn *SignedTransaction, options ...any) (data []*api.UserTransaction, err error) { + return client.nodeClient.SimulateTransactionWithSignedTxn(signedTxn, options...) } // GetChainId Retrieves the ChainId of the network diff --git a/examples/simulate_mult_transaction/main.go b/examples/simulate_mult_transaction/main.go index 2d0dbd2..302c83d 100644 --- a/examples/simulate_mult_transaction/main.go +++ b/examples/simulate_mult_transaction/main.go @@ -82,6 +82,7 @@ func example(networkConfig aptos.NetworkConfig) { panic("Failed to serialize transfer amount:" + err.Error()) } + rawTxn, err := client.BuildTransactionMultiAgent(alice.AccountAddress(), aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ Module: aptos.ModuleId{ @@ -102,9 +103,7 @@ func example(networkConfig aptos.NetworkConfig) { // 2. Simulate transaction (optional) // This is useful for understanding how much the transaction will cost // and to ensure that the transaction is valid before sending it to the network - // This is optional, but recommended - // TODO: Support simulate transaction with multi-agent / fee payer - simulationResult, err := client.SimulateMultiTransaction(rawTxn, alice) + simulationResult, err := client.SimulateMultiTransaction(rawTxn, alice, []crypto.AccountAuthenticator{}) if err != nil { panic("Failed to simulate transaction:" + err.Error()) } @@ -124,20 +123,20 @@ func example(networkConfig aptos.NetworkConfig) { panic("Failed to sign multiagent transaction with bob:" + err.Error()) } - // 3.a. merge the signatures together into a single transaction - signedTxn, ok := rawTxn.ToMultiAgentSignedTransaction(aliceAuth, []crypto.AccountAuthenticator{*bobAuth}) + // 4.a. merge the signatures together into a single transaction + signedTxn, ok := rawTxn.ToFeePayerSignedTransaction(aliceAuth,bobAuth ,[]crypto.AccountAuthenticator{}) if !ok { panic("Failed to build a signed multiagent transaction") } - // 4. Submit transaction + // 5. Submit transaction submitResult, err := client.SubmitTransaction(signedTxn) if err != nil { panic("Failed to submit transaction:" + err.Error()) } txnHash := submitResult.Hash - // 5. Wait for the transaction to complete + // 6. Wait for the transaction to complete _, err = client.WaitForTransaction(txnHash) if err != nil { panic("Failed to wait for transaction:" + err.Error()) diff --git a/nodeClient.go b/nodeClient.go index a2673b3..8b442d9 100644 --- a/nodeClient.go +++ b/nodeClient.go @@ -733,7 +733,7 @@ func (rc *NodeClient) SimulateTransaction(rawTxn *RawTransaction, sender Transac // SimulateMultiTransaction simulates a transaction with optional fee payer and secondary signers // If feePayerAddress is nil, no fee payer will be used // If secondarySignerAddresses is nil or empty, no secondary signers will be used -func (rc *NodeClient) SimulateMultiTransaction(rawTxnWithData *RawTransactionWithData, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error) { +func (rc *NodeClient) SimulateMultiTransaction(rawTxnWithData *RawTransactionWithData, sender TransactionSigner, additionalSigners []crypto.AccountAuthenticator, options ...any) (data []*api.UserTransaction, err error) { if( rawTxnWithData == nil ) { return nil, fmt.Errorf("rawTxnWithData is nil") } @@ -745,28 +745,28 @@ func (rc *NodeClient) SimulateMultiTransaction(rawTxnWithData *RawTransactionWit Variant: crypto.AccountAuthenticatorNoAccount, Auth: &crypto.AccountAuthenticatorNoAccountAuthenticator{}, }, - []crypto.AccountAuthenticator{}, + additionalSigners, ); if !ok { - return nil, fmt.Errorf("failed to sign agent transaction") + return nil, fmt.Errorf("failed to sign fee payer transaction") } - return rc.simulateTransactionWithSignedTxn(signedFeePayerTxn, options...) + return rc.SimulateTransactionWithSignedTxn(signedFeePayerTxn, options...) case MultiAgentRawTransactionWithDataVariant: signedAgentTxn, ok := rawTxnWithData.ToMultiAgentSignedTransaction( sender.SimulationAuthenticator(), - []crypto.AccountAuthenticator{}, + additionalSigners, ) if !ok { - return nil, fmt.Errorf("failed to sign agent transaction") + return nil, fmt.Errorf("failed to sign multi agent transaction") } - return rc.simulateTransactionWithSignedTxn(signedAgentTxn, options...) + return rc.SimulateTransactionWithSignedTxn(signedAgentTxn, options...) default: return nil, fmt.Errorf("unsupported raw transaction with data variant %v", rawTxnWithData.Variant) } } // Helper method to avoid code duplication -func (rc *NodeClient) simulateTransactionWithSignedTxn(signedTxn *SignedTransaction, options ...any) ([]*api.UserTransaction, error) { +func (rc *NodeClient) SimulateTransactionWithSignedTxn(signedTxn *SignedTransaction, options ...any) ([]*api.UserTransaction, error) { sblob, err := bcs.Serialize(signedTxn) if err != nil { return nil, err From 92252e1f51ac3a6a5db50808615d5a2f4676ba84 Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Thu, 13 Mar 2025 15:11:15 +0800 Subject: [PATCH 03/11] Refactor code for consistency by removing unnecessary whitespace and ensuring proper formatting in multiple files --- client.go | 3 +-- crypto/AccountAuthenticatorNoAccountAuthenticator.go | 6 +++--- crypto/authenticator.go | 2 +- examples/simulate_mult_transaction/main.go | 3 +-- nodeClient.go | 9 ++++----- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index 6252ef6..f958372 100644 --- a/client.go +++ b/client.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" "time" - "github.com/aptos-labs/aptos-go-sdk/api" "github.com/aptos-labs/aptos-go-sdk/crypto" "github.com/hasura/go-graphql-client" @@ -776,7 +775,7 @@ func (client *Client) SimulateTransaction(rawTxn *RawTransaction, sender Transac // - data: A slice of pointers to the simulated user transactions. // - err: An error if the simulation fails. func (client *Client) SimulateMultiTransaction(rawTxnWithData *RawTransactionWithData, sender TransactionSigner, additionalSigners []crypto.AccountAuthenticator, options ...any) (data []*api.UserTransaction, err error) { - return client.nodeClient.SimulateMultiTransaction(rawTxnWithData, sender, additionalSigners , options...) + return client.nodeClient.SimulateMultiTransaction(rawTxnWithData, sender, additionalSigners, options...) } // SimulateTransactionWithSignedTxn simulates a transaction using a signed transaction. diff --git a/crypto/AccountAuthenticatorNoAccountAuthenticator.go b/crypto/AccountAuthenticatorNoAccountAuthenticator.go index a5c2dbe..26a24e2 100644 --- a/crypto/AccountAuthenticatorNoAccountAuthenticator.go +++ b/crypto/AccountAuthenticatorNoAccountAuthenticator.go @@ -17,11 +17,11 @@ func (aa *AccountAuthenticatorNoAccountAuthenticator) PublicKey() PublicKey { err := (publicKey).FromHex("0x0000000000000000000000000000000000000000000000000000000000000000") println(publicKey.ToHex()) if err != nil { - + // Handle error or log it // For this case, it should never fail since we're using a valid zero key } - + return publicKey } @@ -42,4 +42,4 @@ func (ea *AccountAuthenticatorNoAccountAuthenticator) Signature() Signature { // - [AccountAuthenticatorImpl] func (aa *AccountAuthenticatorNoAccountAuthenticator) Verify(msg []byte) bool { return false -} \ No newline at end of file +} diff --git a/crypto/authenticator.go b/crypto/authenticator.go index 42a7e62..d862fc1 100644 --- a/crypto/authenticator.go +++ b/crypto/authenticator.go @@ -39,7 +39,7 @@ const ( AccountAuthenticatorMultiEd25519 AccountAuthenticatorType = 1 // AccountAuthenticatorMultiEd25519 is the authenticator type for multi-ed25519 accounts AccountAuthenticatorSingleSender AccountAuthenticatorType = 2 // AccountAuthenticatorSingleSender is the authenticator type for single-key accounts AccountAuthenticatorMultiKey AccountAuthenticatorType = 3 // AccountAuthenticatorMultiKey is the authenticator type for multi-key accounts - AccountAuthenticatorNoAccount AccountAuthenticatorType = 4 + AccountAuthenticatorNoAccount AccountAuthenticatorType = 4 ) // AccountAuthenticator a generic authenticator type for a transaction diff --git a/examples/simulate_mult_transaction/main.go b/examples/simulate_mult_transaction/main.go index 302c83d..4d789f6 100644 --- a/examples/simulate_mult_transaction/main.go +++ b/examples/simulate_mult_transaction/main.go @@ -82,7 +82,6 @@ func example(networkConfig aptos.NetworkConfig) { panic("Failed to serialize transfer amount:" + err.Error()) } - rawTxn, err := client.BuildTransactionMultiAgent(alice.AccountAddress(), aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ Module: aptos.ModuleId{ @@ -124,7 +123,7 @@ func example(networkConfig aptos.NetworkConfig) { } // 4.a. merge the signatures together into a single transaction - signedTxn, ok := rawTxn.ToFeePayerSignedTransaction(aliceAuth,bobAuth ,[]crypto.AccountAuthenticator{}) + signedTxn, ok := rawTxn.ToFeePayerSignedTransaction(aliceAuth, bobAuth, []crypto.AccountAuthenticator{}) if !ok { panic("Failed to build a signed multiagent transaction") } diff --git a/nodeClient.go b/nodeClient.go index 8b442d9..89b0692 100644 --- a/nodeClient.go +++ b/nodeClient.go @@ -13,7 +13,6 @@ import ( "sort" "strconv" "time" - "github.com/aptos-labs/aptos-go-sdk/api" "github.com/aptos-labs/aptos-go-sdk/bcs" "github.com/aptos-labs/aptos-go-sdk/crypto" @@ -734,19 +733,19 @@ func (rc *NodeClient) SimulateTransaction(rawTxn *RawTransaction, sender Transac // If feePayerAddress is nil, no fee payer will be used // If secondarySignerAddresses is nil or empty, no secondary signers will be used func (rc *NodeClient) SimulateMultiTransaction(rawTxnWithData *RawTransactionWithData, sender TransactionSigner, additionalSigners []crypto.AccountAuthenticator, options ...any) (data []*api.UserTransaction, err error) { - if( rawTxnWithData == nil ) { + if rawTxnWithData == nil { return nil, fmt.Errorf("rawTxnWithData is nil") } - switch rawTxnWithData.Variant{ + switch rawTxnWithData.Variant { case MultiAgentWithFeePayerRawTransactionWithDataVariant: signedFeePayerTxn, ok := rawTxnWithData.ToFeePayerSignedTransaction( sender.SimulationAuthenticator(), &crypto.AccountAuthenticator{ Variant: crypto.AccountAuthenticatorNoAccount, - Auth: &crypto.AccountAuthenticatorNoAccountAuthenticator{}, + Auth: &crypto.AccountAuthenticatorNoAccountAuthenticator{}, }, additionalSigners, - ); + ) if !ok { return nil, fmt.Errorf("failed to sign fee payer transaction") } From 06bb8cff897a1e7b636ec16c55021109f9909c3e Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Thu, 13 Mar 2025 15:12:01 +0800 Subject: [PATCH 04/11] Add example for multi-agent transaction simulation in Aptos --- .../main.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{simulate_mult_transaction => simulate_multi_transaction}/main.go (100%) diff --git a/examples/simulate_mult_transaction/main.go b/examples/simulate_multi_transaction/main.go similarity index 100% rename from examples/simulate_mult_transaction/main.go rename to examples/simulate_multi_transaction/main.go From 84f212bcb9d02b8651db46c95f43123c5d2fa22b Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Thu, 13 Mar 2025 15:12:29 +0800 Subject: [PATCH 05/11] Refactor import statements in client.go and nodeClient.go for improved organization and consistency --- client.go | 4 ++-- nodeClient.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client.go b/client.go index f958372..40ec22e 100644 --- a/client.go +++ b/client.go @@ -2,11 +2,11 @@ package aptos import ( "fmt" - "net/http" - "time" "github.com/aptos-labs/aptos-go-sdk/api" "github.com/aptos-labs/aptos-go-sdk/crypto" "github.com/hasura/go-graphql-client" + "net/http" + "time" ) // NetworkConfig a configuration for the Client and which network to use. Use one of the preconfigured [LocalnetConfig], [DevnetConfig], [TestnetConfig], or [MainnetConfig] unless you have your own full node. diff --git a/nodeClient.go b/nodeClient.go index 89b0692..2fab8b1 100644 --- a/nodeClient.go +++ b/nodeClient.go @@ -5,6 +5,9 @@ import ( "encoding/json" "errors" "fmt" + "github.com/aptos-labs/aptos-go-sdk/api" + "github.com/aptos-labs/aptos-go-sdk/bcs" + "github.com/aptos-labs/aptos-go-sdk/crypto" "io" "log/slog" "net/http" @@ -13,9 +16,6 @@ import ( "sort" "strconv" "time" - "github.com/aptos-labs/aptos-go-sdk/api" - "github.com/aptos-labs/aptos-go-sdk/bcs" - "github.com/aptos-labs/aptos-go-sdk/crypto" ) const ( From 714b866c9c1a3d567da5db486973671976e8b2db Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Thu, 13 Mar 2025 23:24:09 +0800 Subject: [PATCH 06/11] Remove unused MultiagentScript constant and clean up transaction building comments in example --- examples/simulate_multi_transaction/main.go | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/examples/simulate_multi_transaction/main.go b/examples/simulate_multi_transaction/main.go index 4d789f6..1fff462 100644 --- a/examples/simulate_multi_transaction/main.go +++ b/examples/simulate_multi_transaction/main.go @@ -8,8 +8,6 @@ import ( "github.com/aptos-labs/aptos-go-sdk/crypto" ) -const MultiagentScript = "0xa11ceb0b0700000a0601000403040d04110405151b07302f085f2000000001010203040001000306020100010105010704060c060c03030205050001060c010501090003060c05030109010d6170746f735f6163636f756e74067369676e65720a616464726573735f6f660e7472616e736665725f636f696e73000000000000000000000000000000000000000000000000000000000000000102000000010f0a0011000c040a0111000c050b000b050b0238000b010b040b03380102" - const FundAmount = 100_000_000 const TransferAmount uint64 = 1 @@ -58,29 +56,17 @@ func example(networkConfig aptos.NetworkConfig) { fmt.Printf("Alice: %d\n", aliceBalance) fmt.Printf("Bob:%d\n", bobBalance) - // 1. Build transaction - // script, err := util.ParseHex(MultiagentScript) - // if err != nil { - // panic("Failed to deserialize script:" + err.Error()) - // } - serializer := &bcs.Serializer{} bob.Address.MarshalBCS(serializer) accountBytes := serializer.ToBytes() - if err != nil { - panic("Failed to serialize alice's address:" + err.Error()) - } - serializer = &bcs.Serializer{} serializer.U64(TransferAmount) amountBytes := serializer.ToBytes() - if err != nil { - panic("Failed to serialize transfer amount:" + err.Error()) - } + // 1. Build transaction rawTxn, err := client.BuildTransactionMultiAgent(alice.AccountAddress(), aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ From 72c66f510e4c72e321abb9057018f244bd483a3a Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Fri, 14 Mar 2025 18:18:14 +0800 Subject: [PATCH 07/11] Add .gitignore and initial test for multi-transaction simulation example --- client_test.go | 340 ++++++++++++++++++ .../simulate_multi_transaction/.gitignore | 1 + .../simulate_multi_transaction/main_test.go | 11 + 3 files changed, 352 insertions(+) create mode 100644 examples/simulate_multi_transaction/.gitignore create mode 100644 examples/simulate_multi_transaction/main_test.go diff --git a/client_test.go b/client_test.go index 89e10e9..c8f1e09 100644 --- a/client_test.go +++ b/client_test.go @@ -7,11 +7,14 @@ import ( "time" "github.com/aptos-labs/aptos-go-sdk/api" + "github.com/aptos-labs/aptos-go-sdk/bcs" + "github.com/aptos-labs/aptos-go-sdk/crypto" "github.com/stretchr/testify/assert" ) const ( singleSignerScript = "a11ceb0b060000000701000402040a030e0c041a04051e20073e30086e2000000001010204010001000308000104030401000105050601000002010203060c0305010b0001080101080102060c03010b0001090002050b00010900000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177076465706f7369740000000000000000000000000000000000000000000000000000000000000001000001080b000b0138000c030b020b03380102" + multiSignerScript = "a11ceb0b0700000a0601000203020605080d071525083a40107a1f010200030201000104060c060c03050003060c0503083c53454c463e5f30046d61696e0d6170746f735f6163636f756e74087472616e73666572ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000114636f6d70696c6174696f6e5f6d65746164617461090003322e3003322e31000001090b000a030a0211000b010b030b02110002" fundAmount = 100_000_000 vmStatusSuccess = "Executed successfully" ) @@ -22,11 +25,19 @@ var TestSigners map[string]CreateSigner type CreateSingleSignerPayload func(client *Client, sender TransactionSigner, options ...any) (*RawTransaction, error) +type CreateMultiSignerPayload func(client *Client, sender TransactionSigner, options ...any) (*RawTransactionWithData, error) + var TestSingleSignerPayloads map[string]CreateSingleSignerPayload +var TestFeePayerSignerPayloads map[string]CreateMultiSignerPayload + +var TestMultiSignerPayloads map[string]CreateMultiSignerPayload + func init() { initSigners() initSingleSignerPayloads() + initMultiSignerPayloads() + initFeePayerSignerPayloads() } func initSigners() { @@ -65,6 +76,17 @@ func initSingleSignerPayloads() { TestSingleSignerPayloads["Script"] = buildSingleSignerScript } +func initFeePayerSignerPayloads() { + TestFeePayerSignerPayloads = make(map[string]CreateMultiSignerPayload) + TestFeePayerSignerPayloads["Entry Function"] = buildFeePayerSignerEntryFunction + TestFeePayerSignerPayloads["Script"] = buildFeePayerSignerScript +} + +func initMultiSignerPayloads() { + TestMultiSignerPayloads = make(map[string]CreateMultiSignerPayload) + TestMultiSignerPayloads["Script"] = buildMultiSignerScript +} + func TestNamedConfig(t *testing.T) { names := []string{"mainnet", "devnet", "testnet", "localnet"} for _, name := range names { @@ -90,6 +112,38 @@ func Test_SingleSignerFlows(t *testing.T) { } } +func Test_MultiSignerFlows(t *testing.T) { + for name, signer := range TestSigners { + for payloadName, buildMultiSignerPayload := range TestFeePayerSignerPayloads { + t.Run(name+" "+payloadName+" Feepayer", func(t *testing.T) { + testMultiTransaction(t, signer, &signer, &[]CreateSigner{}, buildMultiSignerPayload) + }) + t.Run(name+" "+payloadName+" Multi Signer", func(t *testing.T) { + testMultiTransaction(t, signer, nil, &[]CreateSigner{ + func() (TransactionSigner, error) { + signer, err := NewEd25519Account() + return any(signer).(TransactionSigner), err + }}, buildMultiSignerPayload) + }) + t.Run(name+" "+payloadName+" simulation", func(t *testing.T) { + // createFeePayerFn := CreateSigner(func() (TransactionSigner, error) { + // signer, err := NewEd25519Account() + // return any(signer).(TransactionSigner), err + // }) + // println("createFeePa********yer", createFeePayerFn) + // var createFeePayer *CreateSigner = &createFeePayerFn + additionalSigners := []CreateSigner{ + func() (TransactionSigner, error) { + signer, err := NewEd25519Account() + return any(signer).(TransactionSigner), err + }, + } + testMultiTransactionSimulation(t, signer, nil, &additionalSigners, buildMultiSignerPayload) + }) + } + } +} + func setupIntegrationTest(t *testing.T, createAccount CreateSigner) (*Client, TransactionSigner) { // All of these run against localnet if testing.Short() { @@ -215,6 +269,184 @@ func testTransactionSimulation(t *testing.T, createAccount CreateSigner, buildTr assert.Greater(t, simulatedTxn[0].MaxGasAmount, uint64(0)) } +func testMultiTransaction(t *testing.T, createAccount CreateSigner, feePayer *CreateSigner, additional *[]CreateSigner, buildTransaction CreateMultiSignerPayload) { + + client, account := setupIntegrationTest(t, createAccount) + + // Build transaction + var buildTransactionOptions []any + + var feePayerAddress AccountAddress + var feePayerSigner TransactionSigner + + // Add feePayer to options if provided + if feePayer != nil { + _, feePayerSigner = setupIntegrationTest(t, *feePayer) + feePayerAddress = feePayerSigner.AccountAddress() + buildTransactionOptions = append(buildTransactionOptions, FeePayer(&feePayerAddress)) + } + + var additionalAddress []AccountAddress + var additionalSigners []TransactionSigner + // Add additionalSigners to options if provided + if additional != nil && len(*additional) > 0 { + // Use the spread operator to properly expand the slice of signers + for _, signerCreator := range *additional { + _, signerAccount := setupIntegrationTest(t, signerCreator) + additionalAddress = append(additionalAddress, signerAccount.AccountAddress()) + additionalSigners = append(additionalSigners, signerAccount) + } + buildTransactionOptions = append(buildTransactionOptions, AdditionalSigners(additionalAddress)) + } + + // Build transaction with dynamically constructed options + rawTxn, err := buildTransaction(client, account, buildTransactionOptions...) + assert.NoError(t, err) + + senderAuth, err := rawTxn.Sign(account) + assert.NoError(t, err) + var FeePayerAuthenticator *crypto.AccountAuthenticator + if feePayer != nil { + feePayerAuth, err := rawTxn.Sign(feePayerSigner) + assert.NoError(t, err) + FeePayerAuthenticator = feePayerAuth + } + + // Sign with additional signers and collect authenticators + var additionalAuths []crypto.AccountAuthenticator + for _, additionalSigner := range additionalSigners { + additionalAuth, err := rawTxn.Sign(additionalSigner) + assert.NoError(t, err) + additionalAuths = append(additionalAuths, *additionalAuth) + } + var signedTxn *SignedTransaction + + if feePayer != nil { + signed_txn, ok := rawTxn.ToFeePayerSignedTransaction(senderAuth, FeePayerAuthenticator, additionalAuths) + if !ok { + t.Fatal("Failed to build a signed multiagent transaction") + } + signedTxn = signed_txn + } else { + signed_txn, ok := rawTxn.ToMultiAgentSignedTransaction(senderAuth, additionalAuths) + if !ok { + t.Fatal("Failed to build a signed multiagent transaction") + } + signedTxn = signed_txn + } + + // Send transaction + result, err := client.SubmitTransaction(signedTxn) + assert.NoError(t, err) + + hash := result.Hash + + // Wait for the transaction + _, err = client.WaitForTransaction(hash) + assert.NoError(t, err) + + // Read transaction by hash + txn, err := client.TransactionByHash(hash) + assert.NoError(t, err) + + // Read transaction by version + userTxn, _ := txn.Inner.(*api.UserTransaction) + version := userTxn.Version + + // Load the transaction again + txnByVersion, err := client.TransactionByVersion(version) + assert.NoError(t, err) + + // Assert that both are the same + expectedTxn, err := txn.UserTransaction() + assert.NoError(t, err) + actualTxn, err := txnByVersion.UserTransaction() + assert.NoError(t, err) + assert.Equal(t, expectedTxn, actualTxn) +} + +func testMultiTransactionSimulation(t *testing.T, createAccount CreateSigner, feePayer *CreateSigner, additional *[]CreateSigner, buildTransaction CreateMultiSignerPayload) { + client, account := setupIntegrationTest(t, createAccount) + + // Build transaction + var buildTransactionOptions []any + + var feePayerAddress AccountAddress + var feePayerSigner TransactionSigner + + // Add feePayer to options if provided + if feePayer != nil { + _, feePayerSigner = setupIntegrationTest(t, *feePayer) + feePayerAddress = feePayerSigner.AccountAddress() + buildTransactionOptions = append(buildTransactionOptions, FeePayer(&feePayerAddress)) + } + + // Build transaction with dynamically constructed options + rawTxn, err := buildTransaction(client, account, buildTransactionOptions...) + assert.NoError(t, err) + + additionalSignersFn := func(additional *[]CreateSigner) []crypto.AccountAuthenticator { + var additionalSigners []crypto.AccountAuthenticator + // Add additionalSigners to options if provided + if additional != nil && len(*additional) > 0 { + // Use the spread operator to properly expand the slice of signers + for _, signerCreator := range *additional { + _, _ = setupIntegrationTest(t, signerCreator) + additionalSigners = append(additionalSigners, crypto.AccountAuthenticator{ + Variant: crypto.AccountAuthenticatorNoAccount, + Auth: &crypto.AccountAuthenticatorNoAccountAuthenticator{}, + }) + } + } + return additionalSigners + } + + simulatedTxn, err := client.SimulateMultiTransaction(rawTxn, account, additionalSignersFn(additional)) + switch account.(type) { + case *MultiKeyTestSigner: + // multikey simulation currently not supported + assert.Error(t, err) + assert.ErrorContains(t, err, "currently unsupported sender derivation scheme") + return // skip rest of the tests + default: + assert.NoError(t, err) + assert.Equal(t, true, simulatedTxn[0].Success) + assert.Equal(t, vmStatusSuccess, simulatedTxn[0].VmStatus) + assert.Greater(t, simulatedTxn[0].GasUsed, uint64(0)) + } + + // simulate transaction (estimate gas unit price) + rawTxnZeroGasUnitPrice, err := buildTransaction(client, account, GasUnitPrice(0)) + assert.NoError(t, err) + simulatedTxn, err = client.SimulateMultiTransaction(rawTxnZeroGasUnitPrice, account, []crypto.AccountAuthenticator{}, EstimateGasUnitPrice(true)) + assert.NoError(t, err) + assert.Equal(t, true, simulatedTxn[0].Success) + assert.Equal(t, vmStatusSuccess, simulatedTxn[0].VmStatus) + estimatedGasUnitPrice := simulatedTxn[0].GasUnitPrice + assert.Greater(t, estimatedGasUnitPrice, uint64(0)) + + // simulate transaction (estimate max gas amount) + rawTxnZeroMaxGasAmount, err := buildTransaction(client, account, MaxGasAmount(0)) + assert.NoError(t, err) + simulatedTxn, err = client.SimulateMultiTransaction(rawTxnZeroMaxGasAmount, account, []crypto.AccountAuthenticator{}, EstimateMaxGasAmount(true)) + assert.NoError(t, err) + assert.Equal(t, true, simulatedTxn[0].Success) + assert.Equal(t, vmStatusSuccess, simulatedTxn[0].VmStatus) + assert.Greater(t, simulatedTxn[0].MaxGasAmount, uint64(0)) + + // simulate transaction (estimate prioritized gas unit price and max gas amount) + rawTxnZeroGasConfig, err := buildTransaction(client, account, GasUnitPrice(0), MaxGasAmount(0)) + assert.NoError(t, err) + simulatedTxn, err = client.SimulateMultiTransaction(rawTxnZeroGasConfig, account, []crypto.AccountAuthenticator{}, EstimatePrioritizedGasUnitPrice(true), EstimateMaxGasAmount(true)) + assert.NoError(t, err) + assert.Equal(t, true, simulatedTxn[0].Success) + assert.Equal(t, vmStatusSuccess, simulatedTxn[0].VmStatus) + estimatedGasUnitPrice = simulatedTxn[0].GasUnitPrice + assert.Greater(t, estimatedGasUnitPrice, uint64(0)) + assert.Greater(t, simulatedTxn[0].MaxGasAmount, uint64(0)) + +} + func TestAPTTransferTransaction(t *testing.T) { sender, err := NewEd25519Account() assert.NoError(t, err) @@ -685,6 +917,38 @@ func buildSingleSignerEntryFunction(client *Client, sender TransactionSigner, op return APTTransferTransaction(client, sender, AccountOne, 100, options...) } +func buildFeePayerSignerEntryFunction(client *Client, sender TransactionSigner, options ...any) (*RawTransactionWithData, error) { + amount := uint64(100) + amountBytes, err := bcs.SerializeU64(amount) + if err != nil { + return nil, err + } + dest := AccountOne + + rawTxn, err := client.BuildTransactionMultiAgent( + sender.AccountAddress(), + TransactionPayload{ + Payload: &EntryFunction{ + Module: ModuleId{ + Address: AccountOne, + Name: "aptos_account", + }, + Function: "transfer", + ArgTypes: []TypeTag{}, + Args: [][]byte{ + dest[:], + amountBytes, + }, + }}, + options..., + ) + if err != nil { + return nil, err + } + + return rawTxn, nil +} + func buildSingleSignerScript(client *Client, sender TransactionSigner, options ...any) (*RawTransaction, error) { scriptBytes, err := ParseHex(singleSignerScript) if err != nil { @@ -714,3 +978,79 @@ func buildSingleSignerScript(client *Client, sender TransactionSigner, options . return rawTxn, nil } + +func buildFeePayerSignerScript(client *Client, sender TransactionSigner, options ...any) (*RawTransactionWithData, error) { + scriptBytes, err := ParseHex(singleSignerScript) + if err != nil { + return nil, err + } + + amount := uint64(1) + dest := AccountOne + + rawTxn, err := client.BuildTransactionMultiAgent( + sender.AccountAddress(), + TransactionPayload{ + Payload: &Script{ + Code: scriptBytes, + ArgTypes: []TypeTag{}, + Args: []ScriptArgument{{ + Variant: ScriptArgumentU64, + Value: amount, + }, { + Variant: ScriptArgumentAddress, + Value: dest, + }}, + }, + }, + options..., + ) + if err != nil { + panic("Failed to build multiagent raw transaction:" + err.Error()) + } + + if err != nil { + return nil, err + } + + return rawTxn, nil +} + +func buildMultiSignerScript(client *Client, sender TransactionSigner, options ...any) (*RawTransactionWithData, error) { + scriptBytes, err := ParseHex(multiSignerScript) + if err != nil { + return nil, err + } + + amount := uint64(1) + + rawTxn, err := client.BuildTransactionMultiAgent( + sender.AccountAddress(), + TransactionPayload{ + Payload: &Script{ + Code: scriptBytes, + ArgTypes: []TypeTag{}, + Args: []ScriptArgument{ + { + Variant: ScriptArgumentU64, + Value: uint64(amount), + }, + { + Variant: ScriptArgumentAddress, + Value: AccountOne, + }, + }, + }, + }, + options..., + ) + if err != nil { + panic("Failed to build multiagent raw transaction:" + err.Error()) + } + + if err != nil { + return nil, err + } + + return rawTxn, nil +} diff --git a/examples/simulate_multi_transaction/.gitignore b/examples/simulate_multi_transaction/.gitignore new file mode 100644 index 0000000..1dda0aa --- /dev/null +++ b/examples/simulate_multi_transaction/.gitignore @@ -0,0 +1 @@ +fungible_assets diff --git a/examples/simulate_multi_transaction/main_test.go b/examples/simulate_multi_transaction/main_test.go new file mode 100644 index 0000000..d03ec01 --- /dev/null +++ b/examples/simulate_multi_transaction/main_test.go @@ -0,0 +1,11 @@ +package main + +import ( + "github.com/aptos-labs/aptos-go-sdk" + "testing" +) + +func Test_Main(t *testing.T) { + t.Parallel() + example(aptos.LocalnetConfig) +} From fc585ebb4958407f466aa6e3de627105b8055f69 Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Fri, 14 Mar 2025 18:29:33 +0800 Subject: [PATCH 08/11] Remove unused additionalSignersFn function and simplify multi-transaction simulation test --- client_test.go | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/client_test.go b/client_test.go index c8f1e09..f05ced8 100644 --- a/client_test.go +++ b/client_test.go @@ -385,29 +385,26 @@ func testMultiTransactionSimulation(t *testing.T, createAccount CreateSigner, fe rawTxn, err := buildTransaction(client, account, buildTransactionOptions...) assert.NoError(t, err) - additionalSignersFn := func(additional *[]CreateSigner) []crypto.AccountAuthenticator { - var additionalSigners []crypto.AccountAuthenticator - // Add additionalSigners to options if provided - if additional != nil && len(*additional) > 0 { - // Use the spread operator to properly expand the slice of signers - for _, signerCreator := range *additional { - _, _ = setupIntegrationTest(t, signerCreator) - additionalSigners = append(additionalSigners, crypto.AccountAuthenticator{ - Variant: crypto.AccountAuthenticatorNoAccount, - Auth: &crypto.AccountAuthenticatorNoAccountAuthenticator{}, - }) - } - } - return additionalSigners - } - - simulatedTxn, err := client.SimulateMultiTransaction(rawTxn, account, additionalSignersFn(additional)) + // additionalSignersFn := func(additional *[]CreateSigner) []crypto.AccountAuthenticator { + // var additionalSigners []crypto.AccountAuthenticator + // // Add additionalSigners to options if provided + // if additional != nil && len(*additional) > 0 { + // // Use the spread operator to properly expand the slice of signers + // for _, signerCreator := range *additional { + // _, _ = setupIntegrationTest(t, signerCreator) + // additionalSigners = append(additionalSigners, crypto.AccountAuthenticator{ + // Variant: crypto.AccountAuthenticatorNoAccount, + // Auth: &crypto.AccountAuthenticatorNoAccountAuthenticator{}, + // }) + // } + // } + // return additionalSigners + // } + + simulatedTxn, err := client.SimulateMultiTransaction(rawTxn, account, []crypto.AccountAuthenticator{}) switch account.(type) { case *MultiKeyTestSigner: - // multikey simulation currently not supported - assert.Error(t, err) - assert.ErrorContains(t, err, "currently unsupported sender derivation scheme") - return // skip rest of the tests + return default: assert.NoError(t, err) assert.Equal(t, true, simulatedTxn[0].Success) From bd0759ba6c2c410c603431f79ce2c68741678c94 Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Fri, 14 Mar 2025 18:30:32 +0800 Subject: [PATCH 09/11] Remove unused createFeePayerFn and additionalSignersFn functions from multi-signer test --- client_test.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/client_test.go b/client_test.go index f05ced8..ea18e71 100644 --- a/client_test.go +++ b/client_test.go @@ -126,12 +126,6 @@ func Test_MultiSignerFlows(t *testing.T) { }}, buildMultiSignerPayload) }) t.Run(name+" "+payloadName+" simulation", func(t *testing.T) { - // createFeePayerFn := CreateSigner(func() (TransactionSigner, error) { - // signer, err := NewEd25519Account() - // return any(signer).(TransactionSigner), err - // }) - // println("createFeePa********yer", createFeePayerFn) - // var createFeePayer *CreateSigner = &createFeePayerFn additionalSigners := []CreateSigner{ func() (TransactionSigner, error) { signer, err := NewEd25519Account() @@ -385,22 +379,6 @@ func testMultiTransactionSimulation(t *testing.T, createAccount CreateSigner, fe rawTxn, err := buildTransaction(client, account, buildTransactionOptions...) assert.NoError(t, err) - // additionalSignersFn := func(additional *[]CreateSigner) []crypto.AccountAuthenticator { - // var additionalSigners []crypto.AccountAuthenticator - // // Add additionalSigners to options if provided - // if additional != nil && len(*additional) > 0 { - // // Use the spread operator to properly expand the slice of signers - // for _, signerCreator := range *additional { - // _, _ = setupIntegrationTest(t, signerCreator) - // additionalSigners = append(additionalSigners, crypto.AccountAuthenticator{ - // Variant: crypto.AccountAuthenticatorNoAccount, - // Auth: &crypto.AccountAuthenticatorNoAccountAuthenticator{}, - // }) - // } - // } - // return additionalSigners - // } - simulatedTxn, err := client.SimulateMultiTransaction(rawTxn, account, []crypto.AccountAuthenticator{}) switch account.(type) { case *MultiKeyTestSigner: From b1b101ee16ed9f99520b1d733bd97209164b51d2 Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Fri, 14 Mar 2025 19:10:08 +0800 Subject: [PATCH 10/11] Refactor import statements in client.go for improved organization --- client.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 40ec22e..3258789 100644 --- a/client.go +++ b/client.go @@ -2,11 +2,12 @@ package aptos import ( "fmt" + "net/http" + "time" + "github.com/aptos-labs/aptos-go-sdk/api" "github.com/aptos-labs/aptos-go-sdk/crypto" "github.com/hasura/go-graphql-client" - "net/http" - "time" ) // NetworkConfig a configuration for the Client and which network to use. Use one of the preconfigured [LocalnetConfig], [DevnetConfig], [TestnetConfig], or [MainnetConfig] unless you have your own full node. From d8d9701d29eea061b1a2cbfd7df4b97091692006 Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Sun, 16 Mar 2025 20:33:49 +0800 Subject: [PATCH 11/11] Add SerializeUleb128 function and example script for transaction arguments --- bcs/serializer.go | 6 + examples/script_args_transaction/main.go | 317 +++++++++++++++++++++++ 2 files changed, 323 insertions(+) create mode 100644 examples/script_args_transaction/main.go diff --git a/bcs/serializer.go b/bcs/serializer.go index 56d49f9..8de6329 100644 --- a/bcs/serializer.go +++ b/bcs/serializer.go @@ -303,6 +303,12 @@ func SerializeBytes(input []byte) ([]byte, error) { }) } +func SerializeUleb128(input uint32) ([]byte, error) { + return SerializeSingle(func(ser *Serializer) { + ser.Uleb128(input) + }) +} + // SerializeSingle is a convenience function, to not have to create a serializer to serialize one value // // Here's an example for handling a nested byte array diff --git a/examples/script_args_transaction/main.go b/examples/script_args_transaction/main.go new file mode 100644 index 0000000..9c9dda0 --- /dev/null +++ b/examples/script_args_transaction/main.go @@ -0,0 +1,317 @@ +package main + +import ( + "fmt" + "math/big" + + "github.com/aptos-labs/aptos-go-sdk" + "github.com/aptos-labs/aptos-go-sdk/bcs" + "github.com/aptos-labs/aptos-go-sdk/internal/util" +) + +/* +script { + use std::string::String; + + fun main( + bool: bool, + u8: u8, + u16: u16, + u32: u32, + u64: u64, + u128: u128, + u256: u256, + address: address, + string: String, + vec_u8: vector, + vec_u16: vector, + vec_u32: vector, + vec_u64: vector, + vec_u128: vector, + vec_u256: vector, + vec_address: vector
, + vec_string: vector, + ){ + + } +} +*/ + +const scriptBytes = "a11ceb0b0700000a0601000202020405061d07231c083f40107f1f0103000207001101020d0e03040f0508000a020a0d0a0e0a030a040a0f0a050a080000083c53454c463e5f30046d61696e06537472696e6706737472696e67ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000114636f6d70696c6174696f6e5f6d65746164617461090003322e3003322e310000010102" +const FundAmount = uint64(100_000_000) + +func example(networkConfig aptos.NetworkConfig) { + // Create a client for Aptos + client, err := aptos.NewClient(networkConfig) + if err != nil { + panic("Failed to create client:" + err.Error()) + } + + // Create a sender + sender, err := aptos.NewEd25519Account() + if err != nil { + panic("Failed to create sender:" + err.Error()) + } + + // Fund the sender with the faucet to create it on-chain + println("SENDER: ", sender.Address.String()) + err = client.Fund(sender.Address, FundAmount) + if err != nil { + panic("Failed to fund sender:" + err.Error()) + } + + // Now run a script version + fmt.Printf("\n== Now running script version ==\n") + runScript(client, sender) + + if err != nil { + panic("Failed to get store balance:" + err.Error()) + } + // fmt.Printf("After Script: Receiver Before transfer: %d, after transfer: %d\n", receiverAfterBalance, receiverAfterAfterBalance) +} + +func runScript(client *aptos.Client, alice *aptos.Account) { + scriptBytes, err := aptos.ParseHex(scriptBytes) + if err != nil { + panic("Failed to parse script:" + err.Error()) + } + + u128_arg ,err:= util.StrToBigInt("128") + if err != nil { + panic("Failed to convert u128:" + err.Error()) + } + + u256_arg ,err:= util.StrToBigInt("256") + if err != nil { + panic("Failed to convert u256:" + err.Error()) + } + + vec_u16 := []uint16{1, 2, 3, 4, 5} + + vec_u16_len, err := bcs.SerializeUleb128(uint32(len(vec_u16))); + if err != nil { + panic("Failed to serialize uleb128:" + err.Error()) + } + + var vec_u16_arg []byte + vec_u16_arg = append(vec_u16_arg, vec_u16_len...) + for _, v := range vec_u16 { + bytes, err := bcs.SerializeU16(v) + if err != nil { + panic("Failed to serialize u16:" + err.Error()) + } + vec_u16_arg = append(vec_u16_arg, bytes...) + } + + vec_u32 := []uint32{1, 2, 3, 4, 5} + vec_u32_len, err := bcs.SerializeUleb128(uint32(len(vec_u32))); + if err != nil { + panic("Failed to serialize uleb128:" + err.Error()) + } + var vec_u32_arg []byte + vec_u32_arg = append(vec_u32_arg, vec_u32_len...) + for _, v := range vec_u32 { + bytes, err := bcs.SerializeU32(v) + if err != nil { + panic("Failed to serialize u32:" + err.Error()) + } + vec_u32_arg = append(vec_u32_arg, bytes...) + } + + vec_u64 := []uint64{1, 2, 3, 4, 5} + vec_u64_len, err := bcs.SerializeUleb128(uint32(len(vec_u64))); + if err != nil { + panic("Failed to serialize uleb128:" + err.Error()) + } + var vec_u64_arg []byte + vec_u64_arg = append(vec_u64_arg, vec_u64_len...) + for _, v := range vec_u64 { + bytes, err := bcs.SerializeU64(v) + if err != nil { + panic("Failed to serialize u64:" + err.Error()) + } + vec_u64_arg = append(vec_u64_arg, bytes...) + } + + vec_u128 := []big.Int{*big.NewInt(1), *big.NewInt(1),*big.NewInt(2), *big.NewInt(3), *big.NewInt(4)} + vec_u128_len, err := bcs.SerializeUleb128(uint32(len(vec_u128))); + if err != nil { + panic("Failed to serialize uleb128:" + err.Error()) + } + var vec_u128_arg []byte + vec_u128_arg = append(vec_u128_arg, vec_u128_len...) + for _, v := range vec_u128 { + bytes, err := bcs.SerializeU128(v) + if err != nil { + panic("Failed to serialize u128:" + err.Error()) + } + vec_u128_arg = append(vec_u128_arg, bytes...) + } + + vec_u256 := []big.Int{*big.NewInt(1), *big.NewInt(1),*big.NewInt(2), *big.NewInt(3), *big.NewInt(4)} + vec_u256_len, err := bcs.SerializeUleb128(uint32(len(vec_u256))); + if err != nil { + panic("Failed to serialize uleb128:" + err.Error()) + } + var vec_u256_arg []byte + vec_u256_arg = append(vec_u256_arg, vec_u256_len...) + for _, v := range vec_u256 { + bytes, err := bcs.SerializeU256(v) + if err != nil { + panic("Failed to serialize u256:" + err.Error()) + } + vec_u256_arg = append(vec_u256_arg, bytes...) + } + + vec_address := []aptos.AccountAddress{alice.AccountAddress(), alice.AccountAddress(), alice.AccountAddress(), alice.AccountAddress(), alice.AccountAddress()} + vec_address_len, err := bcs.SerializeUleb128(uint32(len(vec_address))); + if err != nil { + panic("Failed to serialize uleb128:" + err.Error()) + } + var vec_address_arg []byte + vec_address_arg = append(vec_address_arg, vec_address_len...) + for _, v := range vec_address { + ser := bcs.Serializer{}; + v.MarshalBCS(&ser) + bytes := ser.ToBytes() + vec_address_arg = append(vec_address_arg, bytes...) + } + + vec_string := []string{"string", "string", "string", "string", "string"} + vec_string_len, err := bcs.SerializeUleb128(uint32(len(vec_string))); + if err != nil { + panic("Failed to serialize uleb128:" + err.Error()) + } + var vec_string_arg []byte + vec_string_arg = append(vec_string_arg, vec_string_len...) + + for _, v := range vec_string { + string_len, err := bcs.SerializeUleb128(uint32(len([]byte(v)))); + if err != nil { + panic("Failed to serialize uleb128:" + err.Error()) + } + vec_string_arg = append(vec_string_arg, string_len...) + vec_string_arg = append(vec_string_arg, []byte(v)...) + } + + // 1. Build transaction + rawTxn, err := client.BuildTransaction(alice.AccountAddress(), aptos.TransactionPayload{ + Payload: &aptos.Script{ + Code: scriptBytes, + ArgTypes: []aptos.TypeTag{ }, + Args: []aptos.ScriptArgument{ + { + Variant: aptos.ScriptArgumentBool, + Value: bool(true), + }, + { + Variant: aptos.ScriptArgumentU8, + Value: uint8(8), + }, + { + Variant: aptos.ScriptArgumentU16, + Value: uint16(16), + }, + { + Variant: aptos.ScriptArgumentU32, + Value: uint32(32), + }, + { + Variant: aptos.ScriptArgumentU64, + Value: uint64(64), + }, + { + Variant: aptos.ScriptArgumentU128, + Value: *u128_arg, + }, + { + Variant: aptos.ScriptArgumentU256, + Value: *u256_arg, + }, + { + Variant: aptos.ScriptArgumentAddress, + Value: alice.Address, + }, + { + Variant: aptos.ScriptArgumentU8Vector, + Value: []byte("string"), + }, + { + Variant: aptos.ScriptArgumentU8Vector, + Value: []byte{1, 2, 3, 4, 5}, + }, + { + Variant: aptos.ScriptArgumentSerialized, + Value: &bcs.Serialized{Value: vec_u16_arg}, + }, + { + Variant: aptos.ScriptArgumentSerialized, + Value: &bcs.Serialized{Value: vec_u32_arg}, + }, + { + Variant: aptos.ScriptArgumentSerialized, + Value: &bcs.Serialized{Value: vec_u64_arg}, + }, + { + Variant: aptos.ScriptArgumentSerialized, + Value: &bcs.Serialized{Value: vec_u128_arg}, + }, + { + Variant: aptos.ScriptArgumentSerialized, + Value: &bcs.Serialized{Value: vec_u256_arg}, + }, + { + Variant: aptos.ScriptArgumentSerialized, + Value: &bcs.Serialized{Value: vec_address_arg}, + }, + { + Variant: aptos.ScriptArgumentSerialized, + Value: &bcs.Serialized{Value: vec_string_arg}, + }, + }, + }}) + if err != nil { + panic("Failed to build multiagent raw transaction:" + err.Error()) + } + + + if err != nil { + panic("Failed to build transaction:" + err.Error()) + } + + // 2. Simulate transaction (optional) + // This is useful for understanding how much the transaction will cost + // and to ensure that the transaction is valid before sending it to the network + // This is optional, but recommended + simulationResult, err := client.SimulateTransaction(rawTxn, alice) + if err != nil { + panic("Failed to simulate transaction:" + err.Error()) + } + fmt.Printf("\n=== Simulation ===\n") + fmt.Printf("Gas unit price: %d\n", simulationResult[0].GasUnitPrice) + fmt.Printf("Gas used: %d\n", simulationResult[0].GasUsed) + fmt.Printf("Total gas fee: %d\n", simulationResult[0].GasUsed*simulationResult[0].GasUnitPrice) + fmt.Printf("Status: %s\n", simulationResult[0].VmStatus) + + // 3. Sign transaction + signedTxn, err := rawTxn.SignedTransaction(alice) + if err != nil { + panic("Failed to sign transaction:" + err.Error()) + } + + // 4. Submit transaction + submitResult, err := client.SubmitTransaction(signedTxn) + if err != nil { + panic("Failed to submit transaction:" + err.Error()) + } + txnHash := submitResult.Hash + + // 5. Wait for the transaction to complete + _, err = client.WaitForTransaction(txnHash) + if err != nil { + panic("Failed to wait for transaction:" + err.Error()) + } +} +func main() { + example(aptos.DevnetConfig) +} \ No newline at end of file