diff --git a/README.md b/README.md index b814067..67ac3dc 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,33 @@ pending, _ := cedra.Transaction.SubmitTransaction(ctx, signedBytes) committed, _ := cedra.WaitForTransaction(ctx, pending.Hash) ``` +## Fee-Payer Transactions + +```go +feePayer, _ := account.NewEd25519AccountFromHex("0xFEE_PAYER_PRIVATE_KEY") + +feePayerTxn, err := cedra.Transaction.BuildFeePayerTransaction(ctx, alice.Address(), transaction.BuildOptions{ + Function: "0x1::cedra_account::transfer", + Args: [][]byte{ + transaction.SerializeAddressArg(bobAddr), + transaction.SerializeU64Arg(500_000), + }, + WithFeePayer: true, +}) + +senderAuthenticator, _ := transaction.SignFeePayerTransactionSenderAuthenticator(feePayerTxn, alice) +feePayerAuthenticator, _ := transaction.SignFeePayerTransactionFeePayerAuthenticator(feePayerTxn, feePayer) +signedBytes, _ := transaction.AssembleFeePayerSignedTransaction( + feePayerTxn, + senderAuthenticator, + feePayer.Address(), + feePayerAuthenticator, +) + +pending, _ := cedra.Transaction.SubmitTransaction(ctx, signedBytes) +committed, _ := cedra.WaitForTransaction(ctx, pending.Hash) +``` + ## ANS ```go diff --git a/api/cedra.go b/api/cedra.go index 61b46a4..8020b59 100644 --- a/api/cedra.go +++ b/api/cedra.go @@ -2,6 +2,7 @@ package api import ( "context" + "fmt" "github.com/celerfi/cedra-go-kit/account" "github.com/celerfi/cedra-go-kit/client" @@ -43,6 +44,9 @@ func NewCedraWithConfig(cfg client.Config) *Cedra { } func (ce *Cedra) SignAndSubmitTransaction(ctx context.Context, signer account.Account, opts transaction.BuildOptions) (*types.CommittedTransaction, error) { + if opts.WithFeePayer || opts.FeePayerAddress != nil { + return nil, fmt.Errorf("fee-payer transactions require SignAndSubmitFeePayerTransaction") + } rawTxn, err := ce.Transaction.BuildTransaction(ctx, signer.Address(), opts) if err != nil { return nil, err @@ -58,6 +62,29 @@ func (ce *Cedra) SignAndSubmitTransaction(ctx context.Context, signer account.Ac return ce.Transaction.WaitForTransaction(ctx, pending.Hash) } +func (ce *Cedra) SignAndSubmitFeePayerTransaction(ctx context.Context, sender account.Account, feePayer account.Account, opts transaction.BuildOptions) (*types.CommittedTransaction, error) { + if !opts.WithFeePayer { + opts.WithFeePayer = true + } + if opts.FeePayerAddress == nil { + addr := feePayer.Address() + opts.FeePayerAddress = &addr + } + rawTxn, err := ce.Transaction.BuildFeePayerTransaction(ctx, sender.Address(), opts) + if err != nil { + return nil, err + } + signedBytes, err := transaction.SignFeePayerTransaction(rawTxn, sender, feePayer) + if err != nil { + return nil, err + } + pending, err := ce.Transaction.SubmitTransaction(ctx, signedBytes) + if err != nil { + return nil, err + } + return ce.Transaction.WaitForTransaction(ctx, pending.Hash) +} + func (ce *Cedra) WaitForTransaction(ctx context.Context, hash string) (*types.CommittedTransaction, error) { return ce.Transaction.WaitForTransaction(ctx, hash) } diff --git a/api/transaction.go b/api/transaction.go index 44613c3..a0ae8d9 100644 --- a/api/transaction.go +++ b/api/transaction.go @@ -66,6 +66,10 @@ func (t *TransactionAPI) BuildTransaction(ctx context.Context, sender account.Ac return t.builder.Build(ctx, sender, opts) } +func (t *TransactionAPI) BuildFeePayerTransaction(ctx context.Context, sender account.AccountAddress, opts transaction.BuildOptions) (*transaction.FeePayerRawTransaction, error) { + return t.builder.BuildFeePayer(ctx, sender, opts) +} + func (t *TransactionAPI) SimulateTransaction(ctx context.Context, rawTxn *transaction.RawTransaction, signer account.Account) ([]types.CommittedTransaction, error) { signedBytes, err := transaction.SimulateTransaction(rawTxn, signer) if err != nil { @@ -78,6 +82,18 @@ func (t *TransactionAPI) SimulateTransaction(ctx context.Context, rawTxn *transa return results, nil } +func (t *TransactionAPI) SimulateFeePayerTransaction(ctx context.Context, rawTxn *transaction.FeePayerRawTransaction, signer account.Account, feePayer account.Account) ([]types.CommittedTransaction, error) { + signedBytes, err := transaction.SimulateFeePayerTransaction(rawTxn, signer, feePayer) + if err != nil { + return nil, err + } + var results []types.CommittedTransaction + if err := t.c.PostBCS(ctx, "/transactions/simulate", signedBytes, &results); err != nil { + return nil, err + } + return results, nil +} + func (t *TransactionAPI) IsTransactionPending(ctx context.Context, hash string) (bool, error) { txn, err := t.GetTransactionByHash(ctx, hash) if err != nil { diff --git a/docs/transactions.md b/docs/transactions.md index 10f8971..9520add 100644 --- a/docs/transactions.md +++ b/docs/transactions.md @@ -47,6 +47,64 @@ fmt.Println(results[0].GasUsed) The simulation endpoint requires a zeroed signature (64 zero bytes). The SDK handles this automatically via `transaction.SimulateTransaction`. +## Fee-payer transactions + +Use `BuildFeePayerTransaction` for sponsored transactions where a second account pays gas. The signing message follows the fee-payer domain: + +``` +sha3_256("CEDRA::RawTransactionWithData") || bcs(feePayerRawTransaction) +``` + +```go +feePayer, _ := account.NewEd25519AccountFromHex("0xFEE_PAYER_PRIVATE_KEY") + +feePayerTxn, err := c.Transaction.BuildFeePayerTransaction(ctx, acct.Address(), transaction.BuildOptions{ + Function: "0x1::cedra_account::transfer", + Args: [][]byte{ + transaction.SerializeAddressArg(recipientAddr), + transaction.SerializeU64Arg(1_000_000), + }, + WithFeePayer: true, +}) + +senderAuthenticator, err := transaction.SignFeePayerTransactionSenderAuthenticator(feePayerTxn, acct) +feePayerAuthenticator, err := transaction.SignFeePayerTransactionFeePayerAuthenticator(feePayerTxn, feePayer) + +signedBytes, err := transaction.AssembleFeePayerSignedTransaction( + feePayerTxn, + senderAuthenticator, + feePayer.Address(), + feePayerAuthenticator, +) +pending, err := c.Transaction.SubmitTransaction(ctx, signedBytes) + +// Or simulate with zeroed signatures first. +results, err := c.Transaction.SimulateFeePayerTransaction(ctx, feePayerTxn, acct, feePayer) +fmt.Println(results[0].GasUsed) +``` + +Browser/backend split: + +```go +// Frontend: +feePayerTxn, _ := c.Transaction.BuildFeePayerTransaction(ctx, acct.Address(), transaction.BuildOptions{ + Function: "0x1::cedra_account::transfer", + Args: [][]byte{transaction.SerializeAddressArg(recipientAddr), transaction.SerializeU64Arg(1_000_000)}, + WithFeePayer: true, +}) +senderAuthenticator, _ := transaction.SignFeePayerTransactionSenderAuthenticator(feePayerTxn, acct) + +// Backend: +feePayerAuthenticator, _ := transaction.SignFeePayerTransactionFeePayerAuthenticator(feePayerTxn, sponsor) +signedBytes, _ := transaction.AssembleFeePayerSignedTransaction( + feePayerTxn, + senderAuthenticator, + sponsor.Address(), + feePayerAuthenticator, +) +pending, _ := c.Transaction.SubmitTransaction(ctx, signedBytes) +``` + ## BuildOptions | Field | Type | Description | @@ -54,6 +112,8 @@ The simulation endpoint requires a zeroed signature (64 zero bytes). The SDK han | `Function` | `string` | Module function e.g. `0x1::aptos_account::transfer` | | `TypeArguments` | `[]string` | Generic type params | | `Arguments` | `[]any` | Function arguments | +| `WithFeePayer` | `bool` | Build a fee-payer/sponsored transaction wrapper | +| `FeePayerAddress` | `*account.AccountAddress` | Optional fee payer address included in the signing wrapper | | `Options` | `*TransactionOptions` | Optional overrides (gas, expiry, sequence number) | ## TransactionOptions diff --git a/transaction/builder.go b/transaction/builder.go index a6b7621..670eaf0 100644 --- a/transaction/builder.go +++ b/transaction/builder.go @@ -13,10 +13,12 @@ import ( ) type BuildOptions struct { - Function string - TypeArgs []string - Args [][]byte - Options *types.TransactionOptions + Function string + TypeArgs []string + Args [][]byte + WithFeePayer bool + FeePayerAddress *account.AccountAddress + Options *types.TransactionOptions } type Builder struct { @@ -28,6 +30,28 @@ func NewBuilder(c *client.Client) *Builder { } func (b *Builder) Build(ctx context.Context, sender account.AccountAddress, opts BuildOptions) (*RawTransaction, error) { + if opts.WithFeePayer { + return nil, fmt.Errorf("builder: with_fee_payer requested; use BuildFeePayer") + } + if opts.FeePayerAddress != nil { + return nil, fmt.Errorf("builder: fee payer address provided; use BuildFeePayer") + } + return b.buildRawTransaction(ctx, sender, opts) +} + +func (b *Builder) BuildFeePayer(ctx context.Context, sender account.AccountAddress, opts BuildOptions) (*FeePayerRawTransaction, error) { + rawTxn, err := b.buildRawTransaction(ctx, sender, opts) + if err != nil { + return nil, err + } + var feePayer account.AccountAddress + if opts.FeePayerAddress != nil { + feePayer = *opts.FeePayerAddress + } + return rawTxn.WithFeePayer(feePayer), nil +} + +func (b *Builder) buildRawTransaction(ctx context.Context, sender account.AccountAddress, opts BuildOptions) (*RawTransaction, error) { var seq uint64 fetchFromNetwork := true diff --git a/transaction/signer.go b/transaction/signer.go index 2dba932..0688587 100644 --- a/transaction/signer.go +++ b/transaction/signer.go @@ -1,6 +1,8 @@ package transaction import ( + "fmt" + "golang.org/x/crypto/sha3" "github.com/celerfi/cedra-go-kit/account" @@ -8,31 +10,75 @@ import ( ) const rawTransactionSalt = "CEDRA::RawTransaction" +const rawTransactionWithDataSalt = "CEDRA::RawTransactionWithData" +const feePayerTransactionAuthenticatorVariant = 3 func SignTransaction(rawTxn *RawTransaction, signer account.Account) ([]byte, error) { - txnBytes := serializeRawTxn(rawTxn) - prefix := sha3.Sum256([]byte(rawTransactionSalt)) - signingMessage := append(prefix[:], txnBytes...) - - authenticator, err := signer.SignTransaction(signingMessage) + authenticator, err := SignTransactionAuthenticator(rawTxn, signer) if err != nil { return nil, err } + return AssembleSignedTransaction(rawTxn, authenticator), nil +} - signed := &bcs.Serializer{} - signed.SerializeFixedBytes(txnBytes) - signed.SerializeFixedBytes(authenticator) - return signed.ToBytes(), nil +func SignFeePayerTransaction(rawTxn *FeePayerRawTransaction, sender account.Account, feePayer account.Account) ([]byte, error) { + senderAuthenticator, err := SignFeePayerTransactionSenderAuthenticator(rawTxn, sender) + if err != nil { + return nil, err + } + feePayerAuthenticator, err := SignFeePayerTransactionFeePayerAuthenticator(rawTxn, feePayer) + if err != nil { + return nil, err + } + return AssembleFeePayerSignedTransaction(rawTxn, senderAuthenticator, feePayer.Address(), feePayerAuthenticator) } func SimulateTransaction(rawTxn *RawTransaction, signer account.Account) ([]byte, error) { - txnBytes := serializeRawTxn(rawTxn) - authenticator := zeroedAuthenticator(signer) + return AssembleSignedTransaction(rawTxn, zeroedAuthenticator(signer)), nil +} - signed := &bcs.Serializer{} - signed.SerializeFixedBytes(txnBytes) - signed.SerializeFixedBytes(authenticator) - return signed.ToBytes(), nil +func SimulateFeePayerTransaction(rawTxn *FeePayerRawTransaction, sender account.Account, feePayer account.Account) ([]byte, error) { + return AssembleFeePayerSignedTransaction(rawTxn, zeroedAuthenticator(sender), feePayer.Address(), zeroedAuthenticator(feePayer)) +} + +func SigningMessage(rawTxn *RawTransaction) []byte { + return signingMessage(rawTransactionSalt, serializeRawTxn(rawTxn)) +} + +func FeePayerSigningMessage(rawTxn *FeePayerRawTransaction) []byte { + return signingMessage(rawTransactionWithDataSalt, serializeFeePayerRawTxn(rawTxn)) +} + +func SignTransactionAuthenticator(rawTxn *RawTransaction, signer account.Account) ([]byte, error) { + return signer.SignTransaction(SigningMessage(rawTxn)) +} + +func SignFeePayerTransactionSenderAuthenticator(rawTxn *FeePayerRawTransaction, signer account.Account) ([]byte, error) { + return signer.SignTransaction(FeePayerSigningMessage(rawTxn)) +} + +func SignFeePayerTransactionFeePayerAuthenticator(rawTxn *FeePayerRawTransaction, signer account.Account) ([]byte, error) { + return signer.SignTransaction(FeePayerSigningMessage(feePayerSigningTransaction(rawTxn, signer.Address()))) +} + +func AssembleSignedTransaction(rawTxn *RawTransaction, authenticator []byte) []byte { + return assembleSignedTransaction(serializeRawTxn(rawTxn), authenticator) +} + +func AssembleFeePayerSignedTransaction(rawTxn *FeePayerRawTransaction, senderAuthenticator []byte, feePayerAddress account.AccountAddress, feePayerAuthenticator []byte) ([]byte, error) { + if rawTxn == nil || rawTxn.RawTransaction == nil { + return nil, fmt.Errorf("transaction: raw transaction is required") + } + + authenticator := &bcs.Serializer{} + authenticator.SerializeULEB128(feePayerTransactionAuthenticatorVariant) + authenticator.SerializeFixedBytes(senderAuthenticator) + authenticator.SerializeULEB128(0) + authenticator.SerializeULEB128(0) + feePayerAddress.Serialize(authenticator) + authenticator.SerializeFixedBytes(feePayerAuthenticator) + + return assembleSignedTransaction(serializeRawTxn(rawTxn.RawTransaction), authenticator.ToBytes()), nil } func serializeRawTxn(rawTxn *RawTransaction) []byte { @@ -41,11 +87,45 @@ func serializeRawTxn(rawTxn *RawTransaction) []byte { return s.ToBytes() } +func serializeFeePayerRawTxn(rawTxn *FeePayerRawTransaction) []byte { + s := &bcs.Serializer{} + rawTxn.Serialize(s) + return s.ToBytes() +} + +func feePayerSigningTransaction(rawTxn *FeePayerRawTransaction, feePayerAddress account.AccountAddress) *FeePayerRawTransaction { + return &FeePayerRawTransaction{ + RawTransaction: rawTxn.RawTransaction, + FeePayerAddress: feePayerAddress, + } +} + func zeroedAuthenticator(signer account.Account) []byte { pubKey := signer.PublicKeyBytes() s := &bcs.Serializer{} - s.SerializeULEB128(0) - s.SerializeBytes(pubKey) - s.SerializeBytes(make([]byte, 64)) + switch signer.(type) { + case *account.SingleKeyAccount: + s.SerializeULEB128(2) + s.SerializeULEB128(1) + s.SerializeBytes(pubKey) + s.SerializeULEB128(1) + s.SerializeBytes(make([]byte, 64)) + default: + s.SerializeULEB128(0) + s.SerializeBytes(pubKey) + s.SerializeBytes(make([]byte, 64)) + } return s.ToBytes() } + +func signingMessage(salt string, txnBytes []byte) []byte { + prefix := sha3.Sum256([]byte(salt)) + return append(prefix[:], txnBytes...) +} + +func assembleSignedTransaction(txnBytes []byte, authenticator []byte) []byte { + signed := &bcs.Serializer{} + signed.SerializeFixedBytes(txnBytes) + signed.SerializeFixedBytes(authenticator) + return signed.ToBytes() +} diff --git a/transaction/signer_test.go b/transaction/signer_test.go index 3aea844..d040181 100644 --- a/transaction/signer_test.go +++ b/transaction/signer_test.go @@ -84,3 +84,77 @@ func TestSignTransactionDifferentSigners(t *testing.T) { t.Error("different signers produced identical signed transactions") } } + +func TestFeePayerSigningMessage(t *testing.T) { + _, feePayerRawTxn, _, _ := makeFixedFeePayerRawTxn(t) + + const wantHex = "dc1cd51bc0103d1c06a90c8ad02db5b5b1c4e4dab7a1f3a96b3a93d7b0cd315901147e4d3a5b10eaed2a93536e284c23096dfcea9ac61f0a8420e5d01fbd8f0ea807000000000000000200000000000000000000000000000000000000000000000000000000000000010d63656472615f6163636f756e74087472616e7366657200022000000000000000000000000000000000000000000000000000000000deadbeef089210000000000000400d030000000000640000000000000000f1536500000000020700000000000000000000000000000000000000000000000000000000000000010a63656472615f636f696e094365647261436f696e00000000000000000000000000000000000000000000000000000000000000000000" + if got := hex.EncodeToString(FeePayerSigningMessage(feePayerRawTxn)); got != wantHex { + t.Fatalf("fee-payer signing message mismatch\nwant: %s\ngot: %s", wantHex, got) + } +} + +func TestSignFeePayerTransactionDeterministic(t *testing.T) { + _, feePayerRawTxn, sender, feePayer := makeFixedFeePayerRawTxn(t) + + signed1, err := SignFeePayerTransaction(feePayerRawTxn, sender, feePayer) + if err != nil { + t.Fatalf("sign fee-payer transaction: %v", err) + } + signed2, err := SignFeePayerTransaction(feePayerRawTxn, sender, feePayer) + if err != nil { + t.Fatalf("sign fee-payer transaction again: %v", err) + } + + const wantHex = "147e4d3a5b10eaed2a93536e284c23096dfcea9ac61f0a8420e5d01fbd8f0ea807000000000000000200000000000000000000000000000000000000000000000000000000000000010d63656472615f6163636f756e74087472616e7366657200022000000000000000000000000000000000000000000000000000000000deadbeef089210000000000000400d030000000000640000000000000000f1536500000000020700000000000000000000000000000000000000000000000000000000000000010a63656472615f636f696e094365647261436f696e00030020d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c977873740ff37089000ccc20d6169f8500e4ea80c74e95b117b338ee6c6e8f17199936a9e75aa15d4adead985cc24e996dfc26b0067aee82c5c719ebc8d64d796b795c7070000a32657fd60acb0433491a33d84823c04722ae76639b272873cc27d015232904e0020a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0409c4cb89287438ee0450dd0917cf80b4069d35d075d6e9b0c02c35883c39e616c130fcf23b5790bcbf5b0b24b17012f2a36deeb63f75500840ea7ba14636b1601" + + got1 := hex.EncodeToString(signed1) + got2 := hex.EncodeToString(signed2) + if got1 != wantHex { + t.Fatalf("fee-payer signed txn mismatch\nwant: %s\ngot: %s", wantHex, got1) + } + if got2 != wantHex { + t.Fatalf("fee-payer signed txn changed across runs\nwant: %s\ngot: %s", wantHex, got2) + } +} + +func TestAssembleFeePayerSignedTransactionUsesExplicitFeePayerAddress(t *testing.T) { + _, feePayerRawTxn, sender, feePayer := makeFixedFeePayerRawTxn(t) + + senderAuthenticator, err := SignFeePayerTransactionSenderAuthenticator(feePayerRawTxn, sender) + if err != nil { + t.Fatalf("sign sender authenticator: %v", err) + } + feePayerAuthenticator, err := SignFeePayerTransactionFeePayerAuthenticator(feePayerRawTxn, feePayer) + if err != nil { + t.Fatalf("sign fee payer authenticator: %v", err) + } + + signed, err := AssembleFeePayerSignedTransaction(feePayerRawTxn, senderAuthenticator, feePayer.Address(), feePayerAuthenticator) + if err != nil { + t.Fatalf("assemble fee payer transaction: %v", err) + } + + authenticator := signed[len(serializeRawTxn(feePayerRawTxn.RawTransaction)):] + if len(authenticator) == 0 || authenticator[0] != 0x03 { + t.Fatalf("expected fee-payer authenticator variant 3, got %x", authenticator) + } +} + +func TestSimulateTransactionSingleKeyAuthenticatorShape(t *testing.T) { + signer, err := account.NewSingleKeyAccountFromHex("0x0101010101010101010101010101010101010101010101010101010101010101") + if err != nil { + t.Fatalf("single key account: %v", err) + } + rawTxn := makeTestRawTxn(t) + + simulated, err := SimulateTransaction(rawTxn, signer) + if err != nil { + t.Fatalf("simulate transaction: %v", err) + } + + authenticator := simulated[len(serializeRawTxn(rawTxn)):] + if len(authenticator) == 0 || authenticator[0] != 0x02 { + t.Fatalf("expected single-key authenticator variant 2, got %x", authenticator) + } +} diff --git a/transaction/transaction_test.go b/transaction/transaction_test.go index b0dd9b4..d0afc70 100644 --- a/transaction/transaction_test.go +++ b/transaction/transaction_test.go @@ -9,6 +9,41 @@ import ( "github.com/celerfi/cedra-go-kit/bcs" ) +func makeFixedFeePayerRawTxn(t *testing.T) (*RawTransaction, *FeePayerRawTransaction, *account.Ed25519Account, *account.Ed25519Account) { + t.Helper() + + sender, err := account.NewEd25519AccountFromHex("0x1111111111111111111111111111111111111111111111111111111111111111") + if err != nil { + t.Fatalf("sender account: %v", err) + } + feePayer, err := account.NewEd25519AccountFromHex("0x2222222222222222222222222222222222222222222222222222222222222222") + if err != nil { + t.Fatalf("fee payer account: %v", err) + } + recipient, _ := account.AccountAddressFromHex("0xdeadbeef") + moduleAddr, _ := account.AccountAddressFromHex("0x1") + + rawTxn := &RawTransaction{ + Sender: sender.Address(), + SequenceNumber: 7, + Payload: &EntryFunction{ + Module: ModuleID{Address: moduleAddr, Name: "cedra_account"}, + Function: "transfer", + TypeArgs: []TypeTag{}, + Args: [][]byte{ + SerializeAddressArg(recipient), + SerializeU64Arg(4242), + }, + }, + MaxGasAmount: 200_000, + GasUnitPrice: 100, + ExpirationTimestampSecs: 1_700_000_000, + ChainID: 2, + } + + return rawTxn, rawTxn.WithFeePayer(account.AccountAddress{}), sender, feePayer +} + func TestSerializeU64Arg(t *testing.T) { b := SerializeU64Arg(1000) if len(b) != 8 { @@ -171,6 +206,18 @@ func TestRawTransactionSigningMessageDeterministic(t *testing.T) { } } +func TestFeePayerRawTransactionSerialize(t *testing.T) { + _, feePayerRawTxn, _, _ := makeFixedFeePayerRawTxn(t) + + s := &bcs.Serializer{} + feePayerRawTxn.Serialize(s) + + const wantHex = "01147e4d3a5b10eaed2a93536e284c23096dfcea9ac61f0a8420e5d01fbd8f0ea807000000000000000200000000000000000000000000000000000000000000000000000000000000010d63656472615f6163636f756e74087472616e7366657200022000000000000000000000000000000000000000000000000000000000deadbeef089210000000000000400d030000000000640000000000000000f1536500000000020700000000000000000000000000000000000000000000000000000000000000010a63656472615f636f696e094365647261436f696e00000000000000000000000000000000000000000000000000000000000000000000" + if got := hex.EncodeToString(s.ToBytes()); got != wantHex { + t.Fatalf("fee-payer raw txn mismatch\nwant: %s\ngot: %s", wantHex, got) + } +} + func TestTypeTagSerialization(t *testing.T) { tags := []TypeTag{ TypeTagBool{}, diff --git a/transaction/types.go b/transaction/types.go index 447e677..86711da 100644 --- a/transaction/types.go +++ b/transaction/types.go @@ -148,6 +148,11 @@ type RawTransaction struct { FeePayerCurrency TypeTag } +type FeePayerRawTransaction struct { + RawTransaction *RawTransaction + FeePayerAddress account.AccountAddress +} + func defaultFeePayerCurrency() TypeTag { addr, _ := account.AccountAddressFromHex("0x1") return TypeTagStruct{Address: addr, Module: "cedra_coin", Name: "CedraCoin"} @@ -169,6 +174,20 @@ func (r *RawTransaction) Serialize(s *bcs.Serializer) { currency.serializeTypeTag(s) } +func (r *RawTransaction) WithFeePayer(feePayer account.AccountAddress) *FeePayerRawTransaction { + return &FeePayerRawTransaction{ + RawTransaction: r, + FeePayerAddress: feePayer, + } +} + +func (r *FeePayerRawTransaction) Serialize(s *bcs.Serializer) { + s.SerializeULEB128(1) + r.RawTransaction.Serialize(s) + s.SerializeULEB128(0) + r.FeePayerAddress.Serialize(s) +} + func SerializeBoolArg(v bool) []byte { s := &bcs.Serializer{} s.SerializeBool(v)