diff --git a/modules/sdk-coin-apt/src/lib/iface.ts b/modules/sdk-coin-apt/src/lib/iface.ts index ff5dce8ba3..0e055a9bb4 100644 --- a/modules/sdk-coin-apt/src/lib/iface.ts +++ b/modules/sdk-coin-apt/src/lib/iface.ts @@ -21,4 +21,5 @@ export interface TransferTxData { gasUnitPrice: number; gasUsed: number; expirationTime: number; + feePayer: string; } diff --git a/modules/sdk-coin-apt/src/lib/transaction/transaction.ts b/modules/sdk-coin-apt/src/lib/transaction/transaction.ts index 1352f1e8b6..777dccc7b9 100644 --- a/modules/sdk-coin-apt/src/lib/transaction/transaction.ts +++ b/modules/sdk-coin-apt/src/lib/transaction/transaction.ts @@ -12,7 +12,6 @@ import { AccountAddress, AccountAuthenticatorEd25519, Aptos, - APTOS_COIN, AptosConfig, DEFAULT_MAX_GAS_AMOUNT, Ed25519PublicKey, @@ -23,7 +22,7 @@ import { RawTransaction, SignedTransaction, SimpleTransaction, - TransactionAuthenticatorEd25519, + TransactionAuthenticatorFeePayer, } from '@aptos-labs/ts-sdk'; import { DEFAULT_GAS_UNIT_PRICE, SECONDS_PER_WEEK, UNAVAILABLE_TEXT } from '../constants'; import utils from '../utils'; @@ -31,7 +30,8 @@ import BigNumber from 'bignumber.js'; export abstract class Transaction extends BaseTransaction { protected _rawTransaction: RawTransaction; - protected _signature: Signature; + protected _senderSignature: Signature; + protected _feePayerSignature: Signature; protected _sender: string; protected _recipient: TransactionRecipient; protected _sequenceNumber: number; @@ -39,6 +39,7 @@ export abstract class Transaction extends BaseTransaction { protected _gasUnitPrice: number; protected _gasUsed: number; protected _expirationTime: number; + protected _feePayerAddress: string; static EMPTY_PUBLIC_KEY = Buffer.alloc(32); static EMPTY_SIGNATURE = Buffer.alloc(64); @@ -50,7 +51,13 @@ export abstract class Transaction extends BaseTransaction { this._gasUsed = 0; this._expirationTime = Math.floor(Date.now() / 1e3) + SECONDS_PER_WEEK; this._sequenceNumber = 0; - this._signature = { + this._senderSignature = { + publicKey: { + pub: Hex.fromHexInput(Transaction.EMPTY_PUBLIC_KEY).toString(), + }, + signature: Transaction.EMPTY_SIGNATURE, + }; + this._feePayerSignature = { publicKey: { pub: Hex.fromHexInput(Transaction.EMPTY_PUBLIC_KEY).toString(), }, @@ -120,6 +127,14 @@ export abstract class Transaction extends BaseTransaction { this._expirationTime = value; } + get feePayerAddress(): string { + return this._feePayerAddress; + } + + set feePayerAddress(value: string) { + this._feePayerAddress = value; + } + set transactionType(transactionType: TransactionType) { this._type = transactionType; } @@ -136,19 +151,38 @@ export abstract class Transaction extends BaseTransaction { } serialize(): string { - const publicKeyBuffer = utils.getBufferFromHexString(this._signature.publicKey.pub); - const publicKey = new Ed25519PublicKey(publicKeyBuffer); + const senderPublicKeyBuffer = utils.getBufferFromHexString(this._senderSignature.publicKey.pub); + const senderPublicKey = new Ed25519PublicKey(senderPublicKeyBuffer); - const signature = new Ed25519Signature(this._signature.signature); + const senderSignature = new Ed25519Signature(this._senderSignature.signature); + const senderAuthenticator = new AccountAuthenticatorEd25519(senderPublicKey, senderSignature); + + const feePayerPublicKeyBuffer = utils.getBufferFromHexString(this._feePayerSignature.publicKey.pub); + const feePayerPublicKey = new Ed25519PublicKey(feePayerPublicKeyBuffer); + + const feePayerSignature = new Ed25519Signature(this._feePayerSignature.signature); + const feePayerAuthenticator = new AccountAuthenticatorEd25519(feePayerPublicKey, feePayerSignature); + + const txnAuthenticator = new TransactionAuthenticatorFeePayer(senderAuthenticator, [], [], { + address: AccountAddress.fromString(this._feePayerAddress), + authenticator: feePayerAuthenticator, + }); - const txnAuthenticator = new TransactionAuthenticatorEd25519(publicKey, signature); const signedTxn = new SignedTransaction(this._rawTransaction, txnAuthenticator); return signedTxn.toString(); } - addSignature(publicKey: PublicKey, signature: Buffer): void { + addSenderSignature(publicKey: PublicKey, signature: Buffer): void { this._signatures = [signature.toString('hex')]; - this._signature = { publicKey, signature }; + this._senderSignature = { publicKey, signature }; + } + + addFeePayerSignature(publicKey: PublicKey, signature: Buffer): void { + this._feePayerSignature = { publicKey, signature }; + } + + addFeePayerAddress(address: string): void { + this._feePayerAddress = address; } async build(): Promise { @@ -197,9 +231,15 @@ export abstract class Transaction extends BaseTransaction { this._rawTransaction = rawTxn; this.loadInputsAndOutputs(); - const authenticator = signedTxn.authenticator as TransactionAuthenticatorEd25519; - const signature = Buffer.from(authenticator.signature.toUint8Array()); - this.addSignature({ pub: authenticator.public_key.toString() }, signature); + const authenticator = signedTxn.authenticator as any; + this._feePayerAddress = authenticator.fee_payer.address.toString(); + const senderSignature = Buffer.from(authenticator.sender.signature.toUint8Array()); + this.addSenderSignature({ pub: authenticator.sender.public_key.toString() }, senderSignature); + const feePayerSignature = Buffer.from(authenticator.fee_payer.authenticator.signature.toUint8Array()); + this.addFeePayerSignature( + { pub: authenticator.fee_payer.authenticator.public_key.toString() }, + feePayerSignature + ); } catch (e) { console.error('invalid signed transaction', e); throw new Error('invalid signed transaction'); @@ -229,8 +269,7 @@ export abstract class Transaction extends BaseTransaction { const simpleTxn = await aptos.transaction.build.simple({ sender: senderAddress, data: { - function: '0x1::coin::transfer', - typeArguments: [APTOS_COIN], + function: '0x1::aptos_account::transfer', functionArguments: [recipientAddress, this.recipient.amount], }, options: { @@ -248,13 +287,22 @@ export abstract class Transaction extends BaseTransaction { } private generateTxnId() { - if (!this._signature || !this._signature.publicKey || !this._signature.signature) { + if ( + !this._senderSignature || + !this._senderSignature.publicKey || + !this._senderSignature.signature || + !this._feePayerSignature.publicKey || + !this._feePayerSignature.signature + ) { return; } const transaction = new SimpleTransaction(this._rawTransaction); - const publicKey = new Ed25519PublicKey(utils.getBufferFromHexString(this._signature.publicKey.pub)); - const signature = new Ed25519Signature(this._signature.signature); - const senderAuthenticator = new AccountAuthenticatorEd25519(publicKey, signature); - this._id = generateUserTransactionHash({ transaction, senderAuthenticator }); + const senderPublicKey = new Ed25519PublicKey(utils.getBufferFromHexString(this._senderSignature.publicKey.pub)); + const senderSignature = new Ed25519Signature(this._senderSignature.signature); + const senderAuthenticator = new AccountAuthenticatorEd25519(senderPublicKey, senderSignature); + const feePayerPublicKey = new Ed25519PublicKey(utils.getBufferFromHexString(this._feePayerSignature.publicKey.pub)); + const feePayerSignature = new Ed25519Signature(this._feePayerSignature.signature); + const feePayerAuthenticator = new AccountAuthenticatorEd25519(feePayerPublicKey, feePayerSignature); + this._id = generateUserTransactionHash({ transaction, senderAuthenticator, feePayerAuthenticator }); } } diff --git a/modules/sdk-coin-apt/src/lib/transaction/transferTransaction.ts b/modules/sdk-coin-apt/src/lib/transaction/transferTransaction.ts index 6432150eec..ae787e8fe2 100644 --- a/modules/sdk-coin-apt/src/lib/transaction/transferTransaction.ts +++ b/modules/sdk-coin-apt/src/lib/transaction/transferTransaction.ts @@ -1,7 +1,12 @@ import { Transaction } from './transaction'; import { AptTransactionExplanation, TransferTxData } from '../iface'; import { TransactionRecipient, TransactionType } from '@bitgo/sdk-core'; -import { generateSigningMessage, RAW_TRANSACTION_SALT } from '@aptos-labs/ts-sdk'; +import { + AccountAddress, + FeePayerRawTransaction, + generateSigningMessage, + RAW_TRANSACTION_WITH_DATA_SALT, +} from '@aptos-labs/ts-sdk'; export class TransferTransaction extends Transaction { constructor(coinConfig) { @@ -10,7 +15,12 @@ export class TransferTransaction extends Transaction { } public get signablePayload(): Buffer { - return Buffer.from(generateSigningMessage(this._rawTransaction.bcsToBytes(), RAW_TRANSACTION_SALT)); + const feePayerRawTxn = new FeePayerRawTransaction( + this._rawTransaction, + [], + AccountAddress.fromString(this._feePayerAddress) + ); + return Buffer.from(generateSigningMessage(feePayerRawTxn.bcsToBytes(), RAW_TRANSACTION_WITH_DATA_SALT)); } /** @inheritDoc */ @@ -52,6 +62,7 @@ export class TransferTransaction extends Transaction { gasUnitPrice: this.gasUnitPrice, gasUsed: this.gasUsed, expirationTime: this.expirationTime, + feePayer: this.feePayerAddress, }; } } diff --git a/modules/sdk-coin-apt/src/lib/transactionBuilder.ts b/modules/sdk-coin-apt/src/lib/transactionBuilder.ts index 143674188c..2a369913b0 100644 --- a/modules/sdk-coin-apt/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-apt/src/lib/transactionBuilder.ts @@ -80,8 +80,12 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { } /** @inheritDoc */ - addSignature(publicKey: BasePublicKey, signature: Buffer): void { - this.transaction.addSignature(publicKey, signature); + addSenderSignature(publicKey: BasePublicKey, signature: Buffer): void { + this.transaction.addSenderSignature(publicKey, signature); + } + + addFeePayerSignature(publicKey: BasePublicKey, signature: Buffer): void { + this.transaction.addFeePayerSignature(publicKey, signature); } /** @inheritdoc */ @@ -151,4 +155,8 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { this.validateValue(new BigNumber(gasData.maxGasAmount)); this.validateValue(new BigNumber(gasData.gasUnitPrice)); } + + addFeePayerAddress(address: string): void { + this.transaction.addFeePayerAddress(address); + } } diff --git a/modules/sdk-coin-apt/test/resources/apt.ts b/modules/sdk-coin-apt/test/resources/apt.ts index e06890c8f1..db856db0d4 100644 --- a/modules/sdk-coin-apt/test/resources/apt.ts +++ b/modules/sdk-coin-apt/test/resources/apt.ts @@ -25,6 +25,17 @@ export const sender2 = { publicKey: '0x2121dcd098069ae535697dd019cfd8677ca7aba0adac1d1959cbce6dc54b1259', }; +export const sender3 = { + address: '0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449', + publicKey: '0xf73836f42257240e43d439552471fc9dbcc3f1af5bd0b4ed83f44b5f66146442', +}; + +export const feePayer = { + address: '0xdbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f2', + privateKey: '0x51a9507d9127841a1465189188d93065cb26bad3bec05d3c8f28e9c1fc35bda0', + publicKey: '0x5223396c531f13e031a9f0cb26d459d799a52e51be9a1cb9e871afb4c31f91ff', +}; + export const recipients: Recipient[] = [ { address: addresses.validAddresses[0], @@ -48,7 +59,7 @@ export const invalidRecipients: Recipient[] = [ ]; export const TRANSFER = - '0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d09170000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d0300000000006400000000000000207c7667000000000200202121dcd098069ae535697dd019cfd8677ca7aba0adac1d1959cbce6dc54b12594010f340ec153b724c4dc1c9a435d0fafed1775d851c1e8d965925a7879550c69a4677925d9198334a72ae7ce8998226ff0a83743c7ba8a2831136c072bf21c404'; + '0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a37244992000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d0300000000006400000000000000979390670000000002030020f73836f42257240e43d439552471fc9dbcc3f1af5bd0b4ed83f44b5f6614644240caeb90efd4b7ecd922c97bb3163e6a9de1fbb2ee0fc0d56af484f4af9b0015c5831341550af29b3686713b6657c821d894635fe13c7933f06ee043728f040b090000dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f200205223396c531f13e031a9f0cb26d459d799a52e51be9a1cb9e871afb4c31f91ff4013e7e8a1325ee5f656c93baa3d0206a1d9bd6da5abdc6f5d9b8bbbb0926ddac68f3e57a915dd217d2d43e776a6cc01af72f895ea712acc836d30349f29a3c606'; export const INVALID_TRANSFER = 'AAAAAAAAAAAAA6e7361637469bc4a58e500b9e64cb6547ee9b403000000000000002064ba1fb2f2fbd2938a350015d601f4db89cd7e8e2370d0dd9ae3ac4f635c1581111b8a49f67370bc4a58e500b9e64cb6462e39b802000000000000002064ba1fb2f2fbd2938a350015d601f4db89cd7e8e2370d0dd9ae3ac47aa1ff81f01c4173a804406a365e69dfb297d4eaaf002546ebd016400000000000000cba4a48bb0f8b586c167e5dcefaa1c5e96ab3f0836d6ca08f2081732944d1e5b6b406a4a462e39b8030000000000000020b9490ede63215262c434e03f606d9799f3ba704523ceda184b386d47aa1ff81f01000000000000006400000000000000'; diff --git a/modules/sdk-coin-apt/test/unit/apt.ts b/modules/sdk-coin-apt/test/unit/apt.ts index fdbcff0870..cdca5be107 100644 --- a/modules/sdk-coin-apt/test/unit/apt.ts +++ b/modules/sdk-coin-apt/test/unit/apt.ts @@ -104,7 +104,7 @@ describe('APT:', function () { describe('Parse and Explain Transactions: ', () => { const transferInputsResponse = [ { - address: testData.sender2.address, + address: testData.sender3.address, amount: testData.AMOUNT.toString(), }, ]; @@ -145,7 +145,7 @@ describe('APT:', function () { 'sender', 'type', ], - id: '0x43ea7697550d5effb68c47488fd32a7756ee418e8d2be7d6b7f634f3ac0d7766', + id: '0x9ec764992194c4b4095289a61073e91cf5404d5bedb5a42ab8bf16d07353332b', outputs: [ { address: '0xf7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad9', @@ -156,7 +156,7 @@ describe('APT:', function () { changeOutputs: [], changeAmount: '0', fee: { fee: '0' }, - sender: '0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d09', + sender: '0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449', type: 0, }); }); diff --git a/modules/sdk-coin-apt/test/unit/transferBuilder.ts b/modules/sdk-coin-apt/test/unit/transferBuilder.ts index 6cdd728563..4477afd51c 100644 --- a/modules/sdk-coin-apt/test/unit/transferBuilder.ts +++ b/modules/sdk-coin-apt/test/unit/transferBuilder.ts @@ -20,6 +20,7 @@ describe('Apt Transfer Transaction', () => { }); txBuilder.sequenceNumber(14); txBuilder.expirationTime(1736246155); + txBuilder.addFeePayerAddress(testData.feePayer.address); const tx = (await txBuilder.build()) as TransferTransaction; should.equal(tx.sender, testData.sender2.address); should.equal(tx.recipient.address, testData.recipients[0].address); @@ -44,7 +45,7 @@ describe('Apt Transfer Transaction', () => { const rawTx = tx.toBroadcastFormat(); should.equal(utils.isValidRawTransaction(rawTx), true); rawTx.should.equal( - '0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d090e0000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d03000000000064000000000000008b037d670000000002002000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + '0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d090e000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d03000000000064000000000000008b037d670000000002030020000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f2002000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' ); }); @@ -54,7 +55,7 @@ describe('Apt Transfer Transaction', () => { should.equal(tx.type, TransactionType.Send); tx.inputs.length.should.equal(1); tx.inputs[0].should.deepEqual({ - address: testData.sender2.address, + address: testData.sender3.address, value: testData.recipients[0].amount, coin: 'tapt', }); @@ -64,11 +65,11 @@ describe('Apt Transfer Transaction', () => { value: testData.recipients[0].amount, coin: 'tapt', }); - should.equal(tx.id, '0x43ea7697550d5effb68c47488fd32a7756ee418e8d2be7d6b7f634f3ac0d7766'); + should.equal(tx.id, '0x9ec764992194c4b4095289a61073e91cf5404d5bedb5a42ab8bf16d07353332b'); should.equal(tx.maxGasAmount, 200000); should.equal(tx.gasUnitPrice, 100); - should.equal(tx.sequenceNumber, 23); - should.equal(tx.expirationTime, 1735818272); + should.equal(tx.sequenceNumber, 146); + should.equal(tx.expirationTime, 1737528215); should.equal(tx.type, TransactionType.Send); const rawTx = tx.toBroadcastFormat(); should.equal(utils.isValidRawTransaction(rawTx), true); @@ -86,11 +87,12 @@ describe('Apt Transfer Transaction', () => { }); txBuilder.sequenceNumber(14); txBuilder.expirationTime(1736246155); + txBuilder.addFeePayerAddress(testData.feePayer.address); const tx = (await txBuilder.build()) as TransferTransaction; const signablePayload = tx.signablePayload; should.equal( signablePayload.toString('hex'), - 'b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b193c8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d090e0000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d03000000000064000000000000008b037d670000000002' + '5efa3c4f02f83a0f4b2d69fc95c607cc02825cc4e7be536ef0992df050d9e67c01c8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d090e000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d03000000000064000000000000008b037d67000000000200dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f2' ); }); @@ -105,6 +107,7 @@ describe('Apt Transfer Transaction', () => { }); txBuilder.sequenceNumber(14); txBuilder.expirationTime(1736246155); + txBuilder.addFeePayerAddress(testData.feePayer.address); const tx = (await txBuilder.build()) as TransferTransaction; const toJson = tx.toJson(); should.equal(toJson.sender, testData.sender2.address); @@ -116,22 +119,23 @@ describe('Apt Transfer Transaction', () => { should.equal(toJson.maxGasAmount, 200000); should.equal(toJson.gasUnitPrice, 100); should.equal(toJson.expirationTime, 1736246155); + should.equal(toJson.feePayer, testData.feePayer.address); }); it('should build a signed tx and validate its toJson', async function () { const txBuilder = factory.from(testData.TRANSFER); const tx = (await txBuilder.build()) as TransferTransaction; const toJson = tx.toJson(); - should.equal(toJson.id, '0x43ea7697550d5effb68c47488fd32a7756ee418e8d2be7d6b7f634f3ac0d7766'); - should.equal(toJson.sender, '0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d09'); + should.equal(toJson.id, '0x9ec764992194c4b4095289a61073e91cf5404d5bedb5a42ab8bf16d07353332b'); + should.equal(toJson.sender, '0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449'); should.deepEqual(toJson.recipient, { address: '0xf7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad9', amount: '1000', }); - should.equal(toJson.sequenceNumber, 23); + should.equal(toJson.sequenceNumber, 146); should.equal(toJson.maxGasAmount, 200000); should.equal(toJson.gasUnitPrice, 100); - should.equal(toJson.expirationTime, 1735818272); + should.equal(toJson.expirationTime, 1737528215); }); }); diff --git a/modules/sdk-coin-apt/test/unit/utils.ts b/modules/sdk-coin-apt/test/unit/utils.ts index 8cec8c40ec..cc2bac72c2 100644 --- a/modules/sdk-coin-apt/test/unit/utils.ts +++ b/modules/sdk-coin-apt/test/unit/utils.ts @@ -38,11 +38,11 @@ describe('Aptos util library', function () { const signedTxn: SignedTransaction = utils.deserializeSignedTransaction(testData.TRANSFER); const rawTx = signedTxn.raw_txn; const recipient = utils.getRecipientFromTransactionPayload(rawTx.payload); - should.equal(rawTx.sender, testData.sender2.address); + should.equal(rawTx.sender.toString(), testData.sender3.address); should.equal(rawTx.max_gas_amount, 200000); should.equal(rawTx.gas_unit_price, 100); - should.equal(rawTx.sequence_number, 23); - should.equal(rawTx.expiration_timestamp_secs, 1735818272); + should.equal(rawTx.sequence_number, 146); + should.equal(rawTx.expiration_timestamp_secs, 1737528215); should.equal(recipient.address, testData.recipients[0].address); should.equal(recipient.amount, testData.recipients[0].amount); });