From 8bbeef64210ff5b5df9e1a168167e363197a690c Mon Sep 17 00:00:00 2001
From: Ellenp2p <Ellenp2p@gmail.com>
Date: Thu, 13 Mar 2025 01:57:51 +0800
Subject: [PATCH 01/10] 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 <Ellenp2p@gmail.com>
Date: Thu, 13 Mar 2025 15:00:32 +0800
Subject: [PATCH 02/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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.