Skip to content

Commit 3b77c20

Browse files
authored
Merge pull request #5428 from BitGo/COIN-2896-apt-transfer-correction
refactor(apt): transaction builder
2 parents 745715b + 21410fc commit 3b77c20

File tree

7 files changed

+165
-114
lines changed

7 files changed

+165
-114
lines changed

modules/sdk-coin-apt/src/lib/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ export const APT_SIGNATURE_LENGTH = 128;
55
export const UNAVAILABLE_TEXT = 'UNAVAILABLE';
66
export const DEFAULT_GAS_UNIT_PRICE = 100;
77
export const SECONDS_PER_WEEK = 7 * 24 * 60 * 60; // Days * Hours * Minutes * Seconds
8+
9+
export const APTOS_ACCOUNT_MODULE = 'aptos_account';
10+
export const FUNGIBLE_ASSET_MODULE = 'fungible_asset';

modules/sdk-coin-apt/src/lib/transaction/transaction.ts

Lines changed: 33 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,17 @@ import {
77
TransactionRecipient,
88
TransactionType,
99
} from '@bitgo/sdk-core';
10-
import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';
10+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
1111
import {
1212
AccountAddress,
1313
AccountAuthenticatorEd25519,
14-
Aptos,
15-
AptosConfig,
1614
DEFAULT_MAX_GAS_AMOUNT,
1715
Ed25519PublicKey,
1816
Ed25519Signature,
1917
FeePayerRawTransaction,
2018
generateSigningMessage,
2119
generateUserTransactionHash,
2220
Hex,
23-
Network,
2421
RAW_TRANSACTION_SALT,
2522
RAW_TRANSACTION_WITH_DATA_SALT,
2623
RawTransaction,
@@ -31,6 +28,7 @@ import {
3128
import { DEFAULT_GAS_UNIT_PRICE, SECONDS_PER_WEEK, UNAVAILABLE_TEXT } from '../constants';
3229
import utils from '../utils';
3330
import BigNumber from 'bignumber.js';
31+
import { AptTransactionExplanation } from '../iface';
3432

3533
export abstract class Transaction extends BaseTransaction {
3634
protected _rawTransaction: RawTransaction;
@@ -214,43 +212,7 @@ export abstract class Transaction extends BaseTransaction {
214212
];
215213
}
216214

217-
fromRawTransaction(rawTransaction: string): void {
218-
let signedTxn: SignedTransaction;
219-
try {
220-
signedTxn = utils.deserializeSignedTransaction(rawTransaction);
221-
} catch (e) {
222-
console.error('invalid raw transaction', e);
223-
throw new Error('invalid raw transaction');
224-
}
225-
this.fromDeserializedSignedTransaction(signedTxn);
226-
}
227-
228-
fromDeserializedSignedTransaction(signedTxn: SignedTransaction): void {
229-
try {
230-
const rawTxn = signedTxn.raw_txn;
231-
this._sender = rawTxn.sender.toString();
232-
this._recipient = utils.getRecipientFromTransactionPayload(rawTxn.payload);
233-
this._sequenceNumber = utils.castToNumber(rawTxn.sequence_number);
234-
this._maxGasAmount = utils.castToNumber(rawTxn.max_gas_amount);
235-
this._gasUnitPrice = utils.castToNumber(rawTxn.gas_unit_price);
236-
this._expirationTime = utils.castToNumber(rawTxn.expiration_timestamp_secs);
237-
this._rawTransaction = rawTxn;
238-
239-
this.loadInputsAndOutputs();
240-
const authenticator = signedTxn.authenticator as TransactionAuthenticatorFeePayer;
241-
this._feePayerAddress = authenticator.fee_payer.address.toString();
242-
const senderAuthenticator = authenticator.sender as AccountAuthenticatorEd25519;
243-
const senderSignature = Buffer.from(senderAuthenticator.signature.toUint8Array());
244-
this.addSenderSignature({ pub: senderAuthenticator.public_key.toString() }, senderSignature);
245-
246-
const feePayerAuthenticator = authenticator.fee_payer.authenticator as AccountAuthenticatorEd25519;
247-
const feePayerSignature = Buffer.from(feePayerAuthenticator.signature.toUint8Array());
248-
this.addFeePayerSignature({ pub: feePayerAuthenticator.public_key.toString() }, feePayerSignature);
249-
} catch (e) {
250-
console.error('invalid signed transaction', e);
251-
throw new Error('invalid signed transaction');
252-
}
253-
}
215+
abstract fromRawTransaction(rawTransaction: string): void;
254216

255217
/**
256218
* Deserializes a signed transaction hex string
@@ -266,27 +228,7 @@ export abstract class Transaction extends BaseTransaction {
266228
}
267229
}
268230

269-
protected async buildRawTransaction() {
270-
const network: Network = this._coinConfig.network.type === NetworkType.MAINNET ? Network.MAINNET : Network.TESTNET;
271-
const aptos = new Aptos(new AptosConfig({ network }));
272-
const senderAddress = AccountAddress.fromString(this._sender);
273-
const recipientAddress = AccountAddress.fromString(this._recipient.address);
274-
275-
const simpleTxn = await aptos.transaction.build.simple({
276-
sender: senderAddress,
277-
data: {
278-
function: '0x1::aptos_account::transfer',
279-
functionArguments: [recipientAddress, this.recipient.amount],
280-
},
281-
options: {
282-
maxGasAmount: this.maxGasAmount,
283-
gasUnitPrice: this.gasUnitPrice,
284-
expireTimestamp: this.expirationTime,
285-
accountSequenceNumber: this.sequenceNumber,
286-
},
287-
});
288-
this._rawTransaction = simpleTxn.rawTransaction;
289-
}
231+
protected abstract buildRawTransaction(): void;
290232

291233
public getFee(): string {
292234
return new BigNumber(this.gasUsed).multipliedBy(this.gasUnitPrice).toString();
@@ -296,6 +238,35 @@ export abstract class Transaction extends BaseTransaction {
296238
return this.feePayerAddress ? this.getSignablePayloadWithFeePayer() : this.getSignablePayloadWithoutFeePayer();
297239
}
298240

241+
/** @inheritDoc */
242+
explainTransaction(): AptTransactionExplanation {
243+
const displayOrder = [
244+
'id',
245+
'outputs',
246+
'outputAmount',
247+
'changeOutputs',
248+
'changeAmount',
249+
'fee',
250+
'withdrawAmount',
251+
'sender',
252+
'type',
253+
];
254+
255+
const outputs: TransactionRecipient[] = [this.recipient];
256+
const outputAmount = outputs[0].amount;
257+
return {
258+
displayOrder,
259+
id: this.id,
260+
outputs,
261+
outputAmount,
262+
changeOutputs: [],
263+
changeAmount: '0',
264+
fee: { fee: this.getFee() },
265+
sender: this.sender,
266+
type: this.type,
267+
};
268+
}
269+
299270
private getSignablePayloadWithFeePayer(): Buffer {
300271
const feePayerRawTxn = new FeePayerRawTransaction(
301272
this._rawTransaction,
Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,24 @@
11
import { Transaction } from './transaction';
2-
import { AptTransactionExplanation, TransferTxData } from '../iface';
3-
import { TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
2+
import { TransferTxData } from '../iface';
3+
import { TransactionType } from '@bitgo/sdk-core';
4+
import {
5+
AccountAddress,
6+
AccountAuthenticatorEd25519,
7+
Aptos,
8+
AptosConfig,
9+
Network,
10+
SignedTransaction,
11+
TransactionAuthenticatorFeePayer,
12+
} from '@aptos-labs/ts-sdk';
13+
import utils from '../utils';
14+
import { NetworkType } from '@bitgo/statics';
415

516
export class TransferTransaction extends Transaction {
617
constructor(coinConfig) {
718
super(coinConfig);
819
this._type = TransactionType.Send;
920
}
1021

11-
/** @inheritDoc */
12-
explainTransaction(): AptTransactionExplanation {
13-
const displayOrder = [
14-
'id',
15-
'outputs',
16-
'outputAmount',
17-
'changeOutputs',
18-
'changeAmount',
19-
'fee',
20-
'withdrawAmount',
21-
'sender',
22-
'type',
23-
];
24-
25-
const outputs: TransactionRecipient[] = [this.recipient];
26-
const outputAmount = outputs[0].amount;
27-
return {
28-
displayOrder,
29-
id: this.id,
30-
outputs,
31-
outputAmount,
32-
changeOutputs: [],
33-
changeAmount: '0',
34-
fee: { fee: this.getFee() },
35-
sender: this.sender,
36-
type: this.type,
37-
};
38-
}
39-
4022
toJson(): TransferTxData {
4123
return {
4224
id: this.id,
@@ -50,4 +32,64 @@ export class TransferTransaction extends Transaction {
5032
feePayer: this.feePayerAddress,
5133
};
5234
}
35+
36+
fromRawTransaction(rawTransaction: string): void {
37+
let signedTxn: SignedTransaction;
38+
try {
39+
signedTxn = utils.deserializeSignedTransaction(rawTransaction);
40+
} catch (e) {
41+
console.error('invalid raw transaction', e);
42+
throw new Error('invalid raw transaction');
43+
}
44+
this.fromDeserializedSignedTransaction(signedTxn);
45+
}
46+
47+
fromDeserializedSignedTransaction(signedTxn: SignedTransaction): void {
48+
try {
49+
const rawTxn = signedTxn.raw_txn;
50+
this._sender = rawTxn.sender.toString();
51+
this._recipient = utils.getRecipientFromTransactionPayload(rawTxn.payload);
52+
this._sequenceNumber = utils.castToNumber(rawTxn.sequence_number);
53+
this._maxGasAmount = utils.castToNumber(rawTxn.max_gas_amount);
54+
this._gasUnitPrice = utils.castToNumber(rawTxn.gas_unit_price);
55+
this._expirationTime = utils.castToNumber(rawTxn.expiration_timestamp_secs);
56+
this._rawTransaction = rawTxn;
57+
58+
this.loadInputsAndOutputs();
59+
const authenticator = signedTxn.authenticator as TransactionAuthenticatorFeePayer;
60+
this._feePayerAddress = authenticator.fee_payer.address.toString();
61+
const senderAuthenticator = authenticator.sender as AccountAuthenticatorEd25519;
62+
const senderSignature = Buffer.from(senderAuthenticator.signature.toUint8Array());
63+
this.addSenderSignature({ pub: senderAuthenticator.public_key.toString() }, senderSignature);
64+
65+
const feePayerAuthenticator = authenticator.fee_payer.authenticator as AccountAuthenticatorEd25519;
66+
const feePayerSignature = Buffer.from(feePayerAuthenticator.signature.toUint8Array());
67+
this.addFeePayerSignature({ pub: feePayerAuthenticator.public_key.toString() }, feePayerSignature);
68+
} catch (e) {
69+
console.error('invalid signed transaction', e);
70+
throw new Error('invalid signed transaction');
71+
}
72+
}
73+
74+
protected async buildRawTransaction(): Promise<void> {
75+
const network: Network = this._coinConfig.network.type === NetworkType.MAINNET ? Network.MAINNET : Network.TESTNET;
76+
const aptos = new Aptos(new AptosConfig({ network }));
77+
const senderAddress = AccountAddress.fromString(this._sender);
78+
const recipientAddress = AccountAddress.fromString(this._recipient.address);
79+
80+
const simpleTxn = await aptos.transaction.build.simple({
81+
sender: senderAddress,
82+
data: {
83+
function: '0x1::aptos_account::transfer',
84+
functionArguments: [recipientAddress, this.recipient.amount],
85+
},
86+
options: {
87+
maxGasAmount: this.maxGasAmount,
88+
gasUnitPrice: this.gasUnitPrice,
89+
expireTimestamp: this.expirationTime,
90+
accountSequenceNumber: this.sequenceNumber,
91+
},
92+
});
93+
this._rawTransaction = simpleTxn.rawTransaction;
94+
}
5395
}

modules/sdk-coin-apt/src/lib/transactionBuilder.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,6 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
8888
this.transaction.addFeePayerSignature(publicKey, signature);
8989
}
9090

91-
/** @inheritdoc */
92-
protected fromImplementation(rawTransaction: string): Transaction {
93-
this.transaction.fromRawTransaction(rawTransaction);
94-
this.transaction.transactionType = this.transactionType;
95-
return this.transaction;
96-
}
97-
98-
/** @inheritdoc */
99-
protected async buildImplementation(): Promise<Transaction> {
100-
this.transaction.transactionType = this.transactionType;
101-
await this.transaction.build();
102-
return this.transaction;
103-
}
104-
10591
/**
10692
* Initialize the transaction builder fields using the decoded transaction data
10793
*

modules/sdk-coin-apt/src/lib/transactionBuilderFactory.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BaseTransactionBuilderFactory } from '@bitgo/sdk-core';
1+
import { BaseTransactionBuilderFactory, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
22
import { TransactionBuilder } from './transactionBuilder';
33
import { TransferBuilder } from './transferBuilder';
44
import utils from './utils';
@@ -17,16 +17,25 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
1717
utils.validateRawTransaction(signedRawTxn);
1818
try {
1919
const signedTxn = this.parseTransaction(signedRawTxn);
20-
// Assumption: only a single transaction type exists
21-
// TODO: add txn type switch case
22-
const transferTx = new TransferTransaction(this._coinConfig);
23-
transferTx.fromDeserializedSignedTransaction(signedTxn);
24-
return this.getTransferBuilder(transferTx);
20+
const txnType = this.getTransactionTypeFromSignedTxn(signedTxn);
21+
switch (txnType) {
22+
case TransactionType.Send:
23+
const transferTx = new TransferTransaction(this._coinConfig);
24+
transferTx.fromDeserializedSignedTransaction(signedTxn);
25+
return this.getTransferBuilder(transferTx);
26+
default:
27+
throw new InvalidTransactionError('Invalid transaction');
28+
}
2529
} catch (e) {
2630
throw e;
2731
}
2832
}
2933

34+
getTransactionTypeFromSignedTxn(signedTxn: SignedTransaction): TransactionType {
35+
const rawTxn = signedTxn.raw_txn;
36+
return utils.getTransactionTypeFromTransactionPayload(rawTxn.payload);
37+
}
38+
3039
/** @inheritdoc */
3140
getTransferBuilder(tx?: Transaction): TransferBuilder {
3241
return this.initializeBuilder(tx, new TransferBuilder(this._coinConfig));

modules/sdk-coin-apt/src/lib/transferBuilder.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TransactionBuilder } from './transactionBuilder';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
33
import { TransactionType } from '@bitgo/sdk-core';
44
import { TransferTransaction } from './transaction/transferTransaction';
5+
import { Transaction } from './transaction/transaction';
56

67
export class TransferBuilder extends TransactionBuilder {
78
constructor(_coinConfig: Readonly<CoinConfig>) {
@@ -21,4 +22,18 @@ export class TransferBuilder extends TransactionBuilder {
2122
initBuilder(tx: TransferTransaction): void {
2223
this._transaction = tx;
2324
}
25+
26+
/** @inheritdoc */
27+
protected fromImplementation(rawTransaction: string): Transaction {
28+
this.transaction.fromRawTransaction(rawTransaction);
29+
this.transaction.transactionType = this.transactionType;
30+
return this.transaction;
31+
}
32+
33+
/** @inheritdoc */
34+
protected async buildImplementation(): Promise<Transaction> {
35+
this.transaction.transactionType = this.transactionType;
36+
await this.transaction.build();
37+
return this.transaction;
38+
}
2439
}

modules/sdk-coin-apt/src/lib/utils.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,21 @@ import {
99
} from '@aptos-labs/ts-sdk';
1010
import {
1111
BaseUtils,
12+
InvalidTransactionError,
1213
isValidEd25519PublicKey,
1314
isValidEd25519SecretKey,
1415
ParseTransactionError,
1516
TransactionRecipient,
17+
TransactionType,
1618
} from '@bitgo/sdk-core';
17-
import { APT_ADDRESS_LENGTH, APT_BLOCK_ID_LENGTH, APT_SIGNATURE_LENGTH, APT_TRANSACTION_ID_LENGTH } from './constants';
19+
import {
20+
APT_ADDRESS_LENGTH,
21+
APT_BLOCK_ID_LENGTH,
22+
APT_SIGNATURE_LENGTH,
23+
APT_TRANSACTION_ID_LENGTH,
24+
APTOS_ACCOUNT_MODULE,
25+
FUNGIBLE_ASSET_MODULE,
26+
} from './constants';
1827
import BigNumber from 'bignumber.js';
1928

2029
export class Utils implements BaseUtils {
@@ -72,6 +81,22 @@ export class Utils implements BaseUtils {
7281
return { address, amount };
7382
}
7483

84+
getTransactionTypeFromTransactionPayload(payload: TransactionPayload): TransactionType {
85+
if (!(payload instanceof TransactionPayloadEntryFunction)) {
86+
throw new Error('Invalid Payload: Expected TransactionPayloadEntryFunction');
87+
}
88+
const entryFunction = payload.entryFunction;
89+
const moduleIdentifier = entryFunction.module_name.name.identifier.trim();
90+
switch (moduleIdentifier) {
91+
case APTOS_ACCOUNT_MODULE:
92+
return TransactionType.Send;
93+
case FUNGIBLE_ASSET_MODULE:
94+
return TransactionType.SendToken;
95+
default:
96+
throw new InvalidTransactionError(`Invalid transaction: unable to fetch transaction type ${moduleIdentifier}`);
97+
}
98+
}
99+
75100
isValidRawTransaction(rawTransaction: string): boolean {
76101
try {
77102
const signedTxn = this.deserializeSignedTransaction(rawTransaction);

0 commit comments

Comments
 (0)