@@ -7,11 +7,14 @@ import (
7
7
"time"
8
8
9
9
"github.com/aptos-labs/aptos-go-sdk/api"
10
+ "github.com/aptos-labs/aptos-go-sdk/bcs"
11
+ "github.com/aptos-labs/aptos-go-sdk/crypto"
10
12
"github.com/stretchr/testify/assert"
11
13
)
12
14
13
15
const (
14
16
singleSignerScript = "a11ceb0b060000000701000402040a030e0c041a04051e20073e30086e2000000001010204010001000308000104030401000105050601000002010203060c0305010b0001080101080102060c03010b0001090002050b00010900000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177076465706f7369740000000000000000000000000000000000000000000000000000000000000001000001080b000b0138000c030b020b03380102"
17
+ multiSignerScript = "a11ceb0b0700000a0601000203020605080d071525083a40107a1f010200030201000104060c060c03050003060c0503083c53454c463e5f30046d61696e0d6170746f735f6163636f756e74087472616e73666572ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000114636f6d70696c6174696f6e5f6d65746164617461090003322e3003322e31000001090b000a030a0211000b010b030b02110002"
15
18
fundAmount = 100_000_000
16
19
vmStatusSuccess = "Executed successfully"
17
20
)
@@ -22,11 +25,19 @@ var TestSigners map[string]CreateSigner
22
25
23
26
type CreateSingleSignerPayload func (client * Client , sender TransactionSigner , options ... any ) (* RawTransaction , error )
24
27
28
+ type CreateMultiSignerPayload func (client * Client , sender TransactionSigner , options ... any ) (* RawTransactionWithData , error )
29
+
25
30
var TestSingleSignerPayloads map [string ]CreateSingleSignerPayload
26
31
32
+ var TestFeePayerSignerPayloads map [string ]CreateMultiSignerPayload
33
+
34
+ var TestMultiSignerPayloads map [string ]CreateMultiSignerPayload
35
+
27
36
func init () {
28
37
initSigners ()
29
38
initSingleSignerPayloads ()
39
+ initMultiSignerPayloads ()
40
+ initFeePayerSignerPayloads ()
30
41
}
31
42
32
43
func initSigners () {
@@ -65,6 +76,17 @@ func initSingleSignerPayloads() {
65
76
TestSingleSignerPayloads ["Script" ] = buildSingleSignerScript
66
77
}
67
78
79
+ func initFeePayerSignerPayloads () {
80
+ TestFeePayerSignerPayloads = make (map [string ]CreateMultiSignerPayload )
81
+ TestFeePayerSignerPayloads ["Entry Function" ] = buildFeePayerSignerEntryFunction
82
+ TestFeePayerSignerPayloads ["Script" ] = buildFeePayerSignerScript
83
+ }
84
+
85
+ func initMultiSignerPayloads () {
86
+ TestMultiSignerPayloads = make (map [string ]CreateMultiSignerPayload )
87
+ TestMultiSignerPayloads ["Script" ] = buildMultiSignerScript
88
+ }
89
+
68
90
func TestNamedConfig (t * testing.T ) {
69
91
names := []string {"mainnet" , "devnet" , "testnet" , "localnet" }
70
92
for _ , name := range names {
@@ -90,6 +112,38 @@ func Test_SingleSignerFlows(t *testing.T) {
90
112
}
91
113
}
92
114
115
+ func Test_MultiSignerFlows (t * testing.T ) {
116
+ for name , signer := range TestSigners {
117
+ for payloadName , buildMultiSignerPayload := range TestFeePayerSignerPayloads {
118
+ t .Run (name + " " + payloadName + " Feepayer" , func (t * testing.T ) {
119
+ testMultiTransaction (t , signer , & signer , & []CreateSigner {}, buildMultiSignerPayload )
120
+ })
121
+ t .Run (name + " " + payloadName + " Multi Signer" , func (t * testing.T ) {
122
+ testMultiTransaction (t , signer , nil , & []CreateSigner {
123
+ func () (TransactionSigner , error ) {
124
+ signer , err := NewEd25519Account ()
125
+ return any (signer ).(TransactionSigner ), err
126
+ }}, buildMultiSignerPayload )
127
+ })
128
+ t .Run (name + " " + payloadName + " simulation" , func (t * testing.T ) {
129
+ // createFeePayerFn := CreateSigner(func() (TransactionSigner, error) {
130
+ // signer, err := NewEd25519Account()
131
+ // return any(signer).(TransactionSigner), err
132
+ // })
133
+ // println("createFeePa********yer", createFeePayerFn)
134
+ // var createFeePayer *CreateSigner = &createFeePayerFn
135
+ additionalSigners := []CreateSigner {
136
+ func () (TransactionSigner , error ) {
137
+ signer , err := NewEd25519Account ()
138
+ return any (signer ).(TransactionSigner ), err
139
+ },
140
+ }
141
+ testMultiTransactionSimulation (t , signer , nil , & additionalSigners , buildMultiSignerPayload )
142
+ })
143
+ }
144
+ }
145
+ }
146
+
93
147
func setupIntegrationTest (t * testing.T , createAccount CreateSigner ) (* Client , TransactionSigner ) {
94
148
// All of these run against localnet
95
149
if testing .Short () {
@@ -215,6 +269,184 @@ func testTransactionSimulation(t *testing.T, createAccount CreateSigner, buildTr
215
269
assert .Greater (t , simulatedTxn [0 ].MaxGasAmount , uint64 (0 ))
216
270
}
217
271
272
+ func testMultiTransaction (t * testing.T , createAccount CreateSigner , feePayer * CreateSigner , additional * []CreateSigner , buildTransaction CreateMultiSignerPayload ) {
273
+
274
+ client , account := setupIntegrationTest (t , createAccount )
275
+
276
+ // Build transaction
277
+ var buildTransactionOptions []any
278
+
279
+ var feePayerAddress AccountAddress
280
+ var feePayerSigner TransactionSigner
281
+
282
+ // Add feePayer to options if provided
283
+ if feePayer != nil {
284
+ _ , feePayerSigner = setupIntegrationTest (t , * feePayer )
285
+ feePayerAddress = feePayerSigner .AccountAddress ()
286
+ buildTransactionOptions = append (buildTransactionOptions , FeePayer (& feePayerAddress ))
287
+ }
288
+
289
+ var additionalAddress []AccountAddress
290
+ var additionalSigners []TransactionSigner
291
+ // Add additionalSigners to options if provided
292
+ if additional != nil && len (* additional ) > 0 {
293
+ // Use the spread operator to properly expand the slice of signers
294
+ for _ , signerCreator := range * additional {
295
+ _ , signerAccount := setupIntegrationTest (t , signerCreator )
296
+ additionalAddress = append (additionalAddress , signerAccount .AccountAddress ())
297
+ additionalSigners = append (additionalSigners , signerAccount )
298
+ }
299
+ buildTransactionOptions = append (buildTransactionOptions , AdditionalSigners (additionalAddress ))
300
+ }
301
+
302
+ // Build transaction with dynamically constructed options
303
+ rawTxn , err := buildTransaction (client , account , buildTransactionOptions ... )
304
+ assert .NoError (t , err )
305
+
306
+ senderAuth , err := rawTxn .Sign (account )
307
+ assert .NoError (t , err )
308
+ var FeePayerAuthenticator * crypto.AccountAuthenticator
309
+ if feePayer != nil {
310
+ feePayerAuth , err := rawTxn .Sign (feePayerSigner )
311
+ assert .NoError (t , err )
312
+ FeePayerAuthenticator = feePayerAuth
313
+ }
314
+
315
+ // Sign with additional signers and collect authenticators
316
+ var additionalAuths []crypto.AccountAuthenticator
317
+ for _ , additionalSigner := range additionalSigners {
318
+ additionalAuth , err := rawTxn .Sign (additionalSigner )
319
+ assert .NoError (t , err )
320
+ additionalAuths = append (additionalAuths , * additionalAuth )
321
+ }
322
+ var signedTxn * SignedTransaction
323
+
324
+ if feePayer != nil {
325
+ signed_txn , ok := rawTxn .ToFeePayerSignedTransaction (senderAuth , FeePayerAuthenticator , additionalAuths )
326
+ if ! ok {
327
+ t .Fatal ("Failed to build a signed multiagent transaction" )
328
+ }
329
+ signedTxn = signed_txn
330
+ } else {
331
+ signed_txn , ok := rawTxn .ToMultiAgentSignedTransaction (senderAuth , additionalAuths )
332
+ if ! ok {
333
+ t .Fatal ("Failed to build a signed multiagent transaction" )
334
+ }
335
+ signedTxn = signed_txn
336
+ }
337
+
338
+ // Send transaction
339
+ result , err := client .SubmitTransaction (signedTxn )
340
+ assert .NoError (t , err )
341
+
342
+ hash := result .Hash
343
+
344
+ // Wait for the transaction
345
+ _ , err = client .WaitForTransaction (hash )
346
+ assert .NoError (t , err )
347
+
348
+ // Read transaction by hash
349
+ txn , err := client .TransactionByHash (hash )
350
+ assert .NoError (t , err )
351
+
352
+ // Read transaction by version
353
+ userTxn , _ := txn .Inner .(* api.UserTransaction )
354
+ version := userTxn .Version
355
+
356
+ // Load the transaction again
357
+ txnByVersion , err := client .TransactionByVersion (version )
358
+ assert .NoError (t , err )
359
+
360
+ // Assert that both are the same
361
+ expectedTxn , err := txn .UserTransaction ()
362
+ assert .NoError (t , err )
363
+ actualTxn , err := txnByVersion .UserTransaction ()
364
+ assert .NoError (t , err )
365
+ assert .Equal (t , expectedTxn , actualTxn )
366
+ }
367
+
368
+ func testMultiTransactionSimulation (t * testing.T , createAccount CreateSigner , feePayer * CreateSigner , additional * []CreateSigner , buildTransaction CreateMultiSignerPayload ) {
369
+ client , account := setupIntegrationTest (t , createAccount )
370
+
371
+ // Build transaction
372
+ var buildTransactionOptions []any
373
+
374
+ var feePayerAddress AccountAddress
375
+ var feePayerSigner TransactionSigner
376
+
377
+ // Add feePayer to options if provided
378
+ if feePayer != nil {
379
+ _ , feePayerSigner = setupIntegrationTest (t , * feePayer )
380
+ feePayerAddress = feePayerSigner .AccountAddress ()
381
+ buildTransactionOptions = append (buildTransactionOptions , FeePayer (& feePayerAddress ))
382
+ }
383
+
384
+ // Build transaction with dynamically constructed options
385
+ rawTxn , err := buildTransaction (client , account , buildTransactionOptions ... )
386
+ assert .NoError (t , err )
387
+
388
+ additionalSignersFn := func (additional * []CreateSigner ) []crypto.AccountAuthenticator {
389
+ var additionalSigners []crypto.AccountAuthenticator
390
+ // Add additionalSigners to options if provided
391
+ if additional != nil && len (* additional ) > 0 {
392
+ // Use the spread operator to properly expand the slice of signers
393
+ for _ , signerCreator := range * additional {
394
+ _ , _ = setupIntegrationTest (t , signerCreator )
395
+ additionalSigners = append (additionalSigners , crypto.AccountAuthenticator {
396
+ Variant : crypto .AccountAuthenticatorNoAccount ,
397
+ Auth : & crypto.AccountAuthenticatorNoAccountAuthenticator {},
398
+ })
399
+ }
400
+ }
401
+ return additionalSigners
402
+ }
403
+
404
+ simulatedTxn , err := client .SimulateMultiTransaction (rawTxn , account , additionalSignersFn (additional ))
405
+ switch account .(type ) {
406
+ case * MultiKeyTestSigner :
407
+ // multikey simulation currently not supported
408
+ assert .Error (t , err )
409
+ assert .ErrorContains (t , err , "currently unsupported sender derivation scheme" )
410
+ return // skip rest of the tests
411
+ default :
412
+ assert .NoError (t , err )
413
+ assert .Equal (t , true , simulatedTxn [0 ].Success )
414
+ assert .Equal (t , vmStatusSuccess , simulatedTxn [0 ].VmStatus )
415
+ assert .Greater (t , simulatedTxn [0 ].GasUsed , uint64 (0 ))
416
+ }
417
+
418
+ // simulate transaction (estimate gas unit price)
419
+ rawTxnZeroGasUnitPrice , err := buildTransaction (client , account , GasUnitPrice (0 ))
420
+ assert .NoError (t , err )
421
+ simulatedTxn , err = client .SimulateMultiTransaction (rawTxnZeroGasUnitPrice , account , []crypto.AccountAuthenticator {}, EstimateGasUnitPrice (true ))
422
+ assert .NoError (t , err )
423
+ assert .Equal (t , true , simulatedTxn [0 ].Success )
424
+ assert .Equal (t , vmStatusSuccess , simulatedTxn [0 ].VmStatus )
425
+ estimatedGasUnitPrice := simulatedTxn [0 ].GasUnitPrice
426
+ assert .Greater (t , estimatedGasUnitPrice , uint64 (0 ))
427
+
428
+ // simulate transaction (estimate max gas amount)
429
+ rawTxnZeroMaxGasAmount , err := buildTransaction (client , account , MaxGasAmount (0 ))
430
+ assert .NoError (t , err )
431
+ simulatedTxn , err = client .SimulateMultiTransaction (rawTxnZeroMaxGasAmount , account , []crypto.AccountAuthenticator {}, EstimateMaxGasAmount (true ))
432
+ assert .NoError (t , err )
433
+ assert .Equal (t , true , simulatedTxn [0 ].Success )
434
+ assert .Equal (t , vmStatusSuccess , simulatedTxn [0 ].VmStatus )
435
+ assert .Greater (t , simulatedTxn [0 ].MaxGasAmount , uint64 (0 ))
436
+
437
+ // simulate transaction (estimate prioritized gas unit price and max gas amount)
438
+ rawTxnZeroGasConfig , err := buildTransaction (client , account , GasUnitPrice (0 ), MaxGasAmount (0 ))
439
+ assert .NoError (t , err )
440
+ simulatedTxn , err = client .SimulateMultiTransaction (rawTxnZeroGasConfig , account , []crypto.AccountAuthenticator {}, EstimatePrioritizedGasUnitPrice (true ), EstimateMaxGasAmount (true ))
441
+ assert .NoError (t , err )
442
+ assert .Equal (t , true , simulatedTxn [0 ].Success )
443
+ assert .Equal (t , vmStatusSuccess , simulatedTxn [0 ].VmStatus )
444
+ estimatedGasUnitPrice = simulatedTxn [0 ].GasUnitPrice
445
+ assert .Greater (t , estimatedGasUnitPrice , uint64 (0 ))
446
+ assert .Greater (t , simulatedTxn [0 ].MaxGasAmount , uint64 (0 ))
447
+
448
+ }
449
+
218
450
func TestAPTTransferTransaction (t * testing.T ) {
219
451
sender , err := NewEd25519Account ()
220
452
assert .NoError (t , err )
@@ -685,6 +917,38 @@ func buildSingleSignerEntryFunction(client *Client, sender TransactionSigner, op
685
917
return APTTransferTransaction (client , sender , AccountOne , 100 , options ... )
686
918
}
687
919
920
+ func buildFeePayerSignerEntryFunction (client * Client , sender TransactionSigner , options ... any ) (* RawTransactionWithData , error ) {
921
+ amount := uint64 (100 )
922
+ amountBytes , err := bcs .SerializeU64 (amount )
923
+ if err != nil {
924
+ return nil , err
925
+ }
926
+ dest := AccountOne
927
+
928
+ rawTxn , err := client .BuildTransactionMultiAgent (
929
+ sender .AccountAddress (),
930
+ TransactionPayload {
931
+ Payload : & EntryFunction {
932
+ Module : ModuleId {
933
+ Address : AccountOne ,
934
+ Name : "aptos_account" ,
935
+ },
936
+ Function : "transfer" ,
937
+ ArgTypes : []TypeTag {},
938
+ Args : [][]byte {
939
+ dest [:],
940
+ amountBytes ,
941
+ },
942
+ }},
943
+ options ... ,
944
+ )
945
+ if err != nil {
946
+ return nil , err
947
+ }
948
+
949
+ return rawTxn , nil
950
+ }
951
+
688
952
func buildSingleSignerScript (client * Client , sender TransactionSigner , options ... any ) (* RawTransaction , error ) {
689
953
scriptBytes , err := ParseHex (singleSignerScript )
690
954
if err != nil {
@@ -714,3 +978,79 @@ func buildSingleSignerScript(client *Client, sender TransactionSigner, options .
714
978
715
979
return rawTxn , nil
716
980
}
981
+
982
+ func buildFeePayerSignerScript (client * Client , sender TransactionSigner , options ... any ) (* RawTransactionWithData , error ) {
983
+ scriptBytes , err := ParseHex (singleSignerScript )
984
+ if err != nil {
985
+ return nil , err
986
+ }
987
+
988
+ amount := uint64 (1 )
989
+ dest := AccountOne
990
+
991
+ rawTxn , err := client .BuildTransactionMultiAgent (
992
+ sender .AccountAddress (),
993
+ TransactionPayload {
994
+ Payload : & Script {
995
+ Code : scriptBytes ,
996
+ ArgTypes : []TypeTag {},
997
+ Args : []ScriptArgument {{
998
+ Variant : ScriptArgumentU64 ,
999
+ Value : amount ,
1000
+ }, {
1001
+ Variant : ScriptArgumentAddress ,
1002
+ Value : dest ,
1003
+ }},
1004
+ },
1005
+ },
1006
+ options ... ,
1007
+ )
1008
+ if err != nil {
1009
+ panic ("Failed to build multiagent raw transaction:" + err .Error ())
1010
+ }
1011
+
1012
+ if err != nil {
1013
+ return nil , err
1014
+ }
1015
+
1016
+ return rawTxn , nil
1017
+ }
1018
+
1019
+ func buildMultiSignerScript (client * Client , sender TransactionSigner , options ... any ) (* RawTransactionWithData , error ) {
1020
+ scriptBytes , err := ParseHex (multiSignerScript )
1021
+ if err != nil {
1022
+ return nil , err
1023
+ }
1024
+
1025
+ amount := uint64 (1 )
1026
+
1027
+ rawTxn , err := client .BuildTransactionMultiAgent (
1028
+ sender .AccountAddress (),
1029
+ TransactionPayload {
1030
+ Payload : & Script {
1031
+ Code : scriptBytes ,
1032
+ ArgTypes : []TypeTag {},
1033
+ Args : []ScriptArgument {
1034
+ {
1035
+ Variant : ScriptArgumentU64 ,
1036
+ Value : uint64 (amount ),
1037
+ },
1038
+ {
1039
+ Variant : ScriptArgumentAddress ,
1040
+ Value : AccountOne ,
1041
+ },
1042
+ },
1043
+ },
1044
+ },
1045
+ options ... ,
1046
+ )
1047
+ if err != nil {
1048
+ panic ("Failed to build multiagent raw transaction:" + err .Error ())
1049
+ }
1050
+
1051
+ if err != nil {
1052
+ return nil , err
1053
+ }
1054
+
1055
+ return rawTxn , nil
1056
+ }
0 commit comments