Skip to content

Commit 7c6b386

Browse files
authored
Merge pull request #5847 from BitGo/COIN-3558-send-many-fungible
feat(sdk-coin-apt): send many support fungible asset
2 parents 722a12c + 66a935f commit 7c6b386

File tree

10 files changed

+305
-136
lines changed

10 files changed

+305
-136
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const AMOUNT_BYTES_LENGTH = 8;
1111
export const DIGITAL_ASSET_TRANSFER_AMOUNT = '1';
1212

1313
export const FUNGIBLE_ASSET_TRANSFER_FUNCTION = '0x1::primary_fungible_store::transfer';
14+
export const FUNGIBLE_ASSET_BATCH_TRANSFER_FUNCTION = '0x1::aptos_account::batch_transfer_fungible_assets';
1415
export const COIN_TRANSFER_FUNCTION = '0x1::aptos_account::transfer_coins';
1516
export const COIN_BATCH_TRANSFER_FUNCTION = '0x1::aptos_account::batch_transfer_coins';
1617
export const DIGITAL_ASSET_TRANSFER_FUNCTION = '0x1::object::transfer';
Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { Transaction } from './transaction';
22
import {
33
AccountAddress,
4-
Aptos,
5-
AptosConfig,
64
EntryFunctionABI,
5+
InputGenerateTransactionPayloadData,
76
MoveAbility,
8-
Network,
97
objectStructTag,
108
TransactionPayload,
119
TransactionPayloadEntryFunction,
@@ -14,7 +12,7 @@ import {
1412
TypeTagStruct,
1513
} from '@aptos-labs/ts-sdk';
1614
import { InvalidTransactionError, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
17-
import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';
15+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
1816
import {
1917
DIGITAL_ASSET_TYPE_ARGUMENT,
2018
DIGITAL_ASSET_TRANSFER_FUNCTION,
@@ -46,33 +44,19 @@ export class DigitalAssetTransfer extends Transaction {
4644
] as TransactionRecipient[];
4745
}
4846

49-
protected async buildRawTransaction(): Promise<void> {
50-
const network: Network = this._coinConfig.network.type === NetworkType.MAINNET ? Network.MAINNET : Network.TESTNET;
51-
const aptos = new Aptos(new AptosConfig({ network }));
52-
const senderAddress = AccountAddress.fromString(this._sender);
47+
protected getTransactionPayloadData(): InputGenerateTransactionPayloadData {
5348
const recipientAddress = AccountAddress.fromString(this.recipients[0].address);
5449
const digitalAssetAddress = AccountAddress.fromString(this._assetId);
55-
5650
const transferDigitalAssetAbi: EntryFunctionABI = {
5751
typeParameters: [{ constraints: [MoveAbility.KEY] }],
5852
parameters: [new TypeTagStruct(objectStructTag(new TypeTagGeneric(0))), new TypeTagAddress()],
5953
};
6054

61-
const simpleTxn = await aptos.transaction.build.simple({
62-
sender: senderAddress,
63-
data: {
64-
function: DIGITAL_ASSET_TRANSFER_FUNCTION,
65-
typeArguments: [DIGITAL_ASSET_TYPE_ARGUMENT],
66-
functionArguments: [digitalAssetAddress, recipientAddress],
67-
abi: transferDigitalAssetAbi,
68-
},
69-
options: {
70-
maxGasAmount: this.maxGasAmount,
71-
gasUnitPrice: this.gasUnitPrice,
72-
expireTimestamp: this.expirationTime,
73-
accountSequenceNumber: this.sequenceNumber,
74-
},
75-
});
76-
this._rawTransaction = simpleTxn.rawTransaction;
55+
return {
56+
function: DIGITAL_ASSET_TRANSFER_FUNCTION,
57+
typeArguments: [DIGITAL_ASSET_TYPE_ARGUMENT],
58+
functionArguments: [digitalAssetAddress, recipientAddress],
59+
abi: transferDigitalAssetAbi,
60+
};
7761
}
7862
}
Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import { Transaction } from './transaction';
22
import {
33
AccountAddress,
4-
Aptos,
5-
AptosConfig,
64
EntryFunctionABI,
7-
Network,
5+
InputGenerateTransactionPayloadData,
86
parseTypeTag,
97
TransactionPayload,
108
TransactionPayloadEntryFunction,
119
TypeTagAddress,
1210
TypeTagU64,
1311
} from '@aptos-labs/ts-sdk';
14-
import { InvalidTransactionError, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
15-
import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';
16-
import { FUNGIBLE_ASSET_TYPE_ARGUMENT, FUNGIBLE_ASSET_TRANSFER_FUNCTION } from '../constants';
12+
import { InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
13+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
14+
import {
15+
FUNGIBLE_ASSET_TYPE_ARGUMENT,
16+
FUNGIBLE_ASSET_TRANSFER_FUNCTION,
17+
FUNGIBLE_ASSET_BATCH_TRANSFER_FUNCTION,
18+
} from '../constants';
1719
import utils from '../utils';
1820

1921
export class FungibleAssetTransfer extends Transaction {
@@ -23,50 +25,53 @@ export class FungibleAssetTransfer extends Transaction {
2325
}
2426

2527
protected parseTransactionPayload(payload: TransactionPayload): void {
26-
if (
27-
!(payload instanceof TransactionPayloadEntryFunction) ||
28-
payload.entryFunction.args.length !== 3 ||
29-
payload.entryFunction.type_args.length !== 1 ||
30-
FUNGIBLE_ASSET_TYPE_ARGUMENT !== payload.entryFunction.type_args[0].toString()
31-
) {
28+
if (!this.isValidPayload(payload)) {
3229
throw new InvalidTransactionError('Invalid transaction payload');
3330
}
34-
const entryFunction = payload.entryFunction;
31+
const entryFunction = (payload as TransactionPayloadEntryFunction).entryFunction;
3532
this._assetId = entryFunction.args[0].toString();
36-
this.recipients = [
37-
{
38-
address: entryFunction.args[1].toString(),
39-
amount: utils.getAmountFromPayloadArgs(entryFunction.args[2].bcsToBytes()),
40-
},
41-
] as TransactionRecipient[];
33+
const addressArg = entryFunction.args[1];
34+
const amountArg = entryFunction.args[2];
35+
this.recipients = utils.parseRecipients(addressArg, amountArg);
4236
}
4337

44-
protected async buildRawTransaction(): Promise<void> {
45-
const network: Network = this._coinConfig.network.type === NetworkType.MAINNET ? Network.MAINNET : Network.TESTNET;
46-
const aptos = new Aptos(new AptosConfig({ network }));
47-
const senderAddress = AccountAddress.fromString(this._sender);
48-
const recipientAddress = AccountAddress.fromString(this.recipients[0].address);
38+
protected getTransactionPayloadData(): InputGenerateTransactionPayloadData {
4939
const fungibleTokenAddress = this._assetId;
5040
const faTransferAbi: EntryFunctionABI = {
5141
typeParameters: [{ constraints: [] }],
5242
parameters: [parseTypeTag('0x1::object::Object'), new TypeTagAddress(), new TypeTagU64()],
5343
};
5444

55-
const simpleTxn = await aptos.transaction.build.simple({
56-
sender: senderAddress,
57-
data: {
58-
function: FUNGIBLE_ASSET_TRANSFER_FUNCTION,
59-
typeArguments: [FUNGIBLE_ASSET_TYPE_ARGUMENT],
60-
functionArguments: [fungibleTokenAddress, recipientAddress, this.recipients[0].amount],
61-
abi: faTransferAbi,
62-
},
63-
options: {
64-
maxGasAmount: this.maxGasAmount,
65-
gasUnitPrice: this.gasUnitPrice,
66-
expireTimestamp: this.expirationTime,
67-
accountSequenceNumber: this.sequenceNumber,
68-
},
69-
});
70-
this._rawTransaction = simpleTxn.rawTransaction;
45+
if (this.recipients.length > 1) {
46+
return {
47+
function: FUNGIBLE_ASSET_BATCH_TRANSFER_FUNCTION,
48+
typeArguments: [],
49+
functionArguments: [
50+
fungibleTokenAddress,
51+
this.recipients.map((recipient) => AccountAddress.fromString(recipient.address)),
52+
this.recipients.map((recipient) => recipient.amount),
53+
],
54+
};
55+
}
56+
return {
57+
function: FUNGIBLE_ASSET_TRANSFER_FUNCTION,
58+
typeArguments: [FUNGIBLE_ASSET_TYPE_ARGUMENT],
59+
functionArguments: [
60+
fungibleTokenAddress,
61+
AccountAddress.fromString(this.recipients[0].address),
62+
this.recipients[0].amount,
63+
],
64+
abi: faTransferAbi,
65+
};
66+
}
67+
68+
private isValidPayload(payload: TransactionPayload) {
69+
return (
70+
payload instanceof TransactionPayloadEntryFunction &&
71+
payload.entryFunction.args.length === 3 &&
72+
(payload.entryFunction.type_args.length === 0 ||
73+
(payload.entryFunction.type_args.length === 1 &&
74+
FUNGIBLE_ASSET_TYPE_ARGUMENT === payload.entryFunction.type_args[0].toString()))
75+
);
7176
}
7277
}

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@ import {
77
TransactionRecipient,
88
TransactionType,
99
} from '@bitgo/sdk-core';
10-
import { BaseCoin as CoinConfig } from '@bitgo/statics';
10+
import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';
1111
import {
1212
AccountAddress,
1313
AccountAuthenticator,
1414
AccountAuthenticatorEd25519,
1515
AccountAuthenticatorNoAccountAuthenticator,
16+
Aptos,
17+
AptosConfig,
1618
DEFAULT_MAX_GAS_AMOUNT,
1719
Ed25519PublicKey,
1820
Ed25519Signature,
1921
FeePayerRawTransaction,
2022
generateSigningMessage,
2123
generateUserTransactionHash,
2224
Hex,
25+
InputGenerateTransactionPayloadData,
26+
Network,
2327
RAW_TRANSACTION_SALT,
2428
RAW_TRANSACTION_WITH_DATA_SALT,
2529
RawTransaction,
@@ -179,7 +183,7 @@ export abstract class Transaction extends BaseTransaction {
179183
this._isSimulateTxn = value;
180184
}
181185

182-
protected abstract buildRawTransaction(): void;
186+
protected abstract getTransactionPayloadData(): InputGenerateTransactionPayloadData;
183187

184188
protected abstract parseTransactionPayload(payload: TransactionPayload): void;
185189

@@ -374,6 +378,24 @@ export abstract class Transaction extends BaseTransaction {
374378
};
375379
}
376380

381+
protected async buildRawTransaction(): Promise<void> {
382+
const network: Network = this._coinConfig.network.type === NetworkType.MAINNET ? Network.MAINNET : Network.TESTNET;
383+
const aptos = new Aptos(new AptosConfig({ network }));
384+
const senderAddress = AccountAddress.fromString(this._sender);
385+
386+
const simpleTxn = await aptos.transaction.build.simple({
387+
sender: senderAddress,
388+
data: this.getTransactionPayloadData() as InputGenerateTransactionPayloadData,
389+
options: {
390+
maxGasAmount: this.maxGasAmount,
391+
gasUnitPrice: this.gasUnitPrice,
392+
expireTimestamp: this.expirationTime,
393+
accountSequenceNumber: this.sequenceNumber,
394+
},
395+
});
396+
this._rawTransaction = simpleTxn.rawTransaction;
397+
}
398+
377399
private getSignablePayloadWithFeePayer(): Buffer {
378400
const feePayerRawTxn = new FeePayerRawTransaction(
379401
this._rawTransaction,
Lines changed: 19 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import { Transaction } from './transaction';
2-
import { InvalidTransactionError, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
2+
import { InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
33
import {
44
AccountAddress,
5-
Aptos,
6-
AptosConfig,
7-
EntryFunctionArgument,
85
InputGenerateTransactionPayloadData,
9-
Network,
106
TransactionPayload,
117
TransactionPayloadEntryFunction,
128
} from '@aptos-labs/ts-sdk';
139

14-
import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';
10+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
1511
import { APTOS_COIN, COIN_BATCH_TRANSFER_FUNCTION, COIN_TRANSFER_FUNCTION } from '../constants';
1612
import utils from '../utils';
1713

@@ -30,25 +26,25 @@ export class TransferTransaction extends Transaction {
3026
this._assetId = entryFunction.type_args[0].toString();
3127
const addressArg = entryFunction.args[0];
3228
const amountArg = entryFunction.args[1];
33-
this.recipients = this.parseRecipients(addressArg, amountArg);
29+
this.recipients = utils.parseRecipients(addressArg, amountArg);
3430
}
3531

36-
protected async buildRawTransaction(): Promise<void> {
37-
const network: Network = this._coinConfig.network.type === NetworkType.MAINNET ? Network.MAINNET : Network.TESTNET;
38-
const aptos = new Aptos(new AptosConfig({ network }));
39-
const senderAddress = AccountAddress.fromString(this._sender);
40-
41-
const simpleTxn = await aptos.transaction.build.simple({
42-
sender: senderAddress,
43-
data: this.buildData(this.recipients) as InputGenerateTransactionPayloadData,
44-
options: {
45-
maxGasAmount: this.maxGasAmount,
46-
gasUnitPrice: this.gasUnitPrice,
47-
expireTimestamp: this.expirationTime,
48-
accountSequenceNumber: this.sequenceNumber,
49-
},
50-
});
51-
this._rawTransaction = simpleTxn.rawTransaction;
32+
protected getTransactionPayloadData(): InputGenerateTransactionPayloadData {
33+
if (this.recipients.length > 1) {
34+
return {
35+
function: COIN_BATCH_TRANSFER_FUNCTION,
36+
typeArguments: [this.assetId],
37+
functionArguments: [
38+
this.recipients.map((recipient) => AccountAddress.fromString(recipient.address)),
39+
this.recipients.map((recipient) => recipient.amount),
40+
],
41+
};
42+
}
43+
return {
44+
function: COIN_TRANSFER_FUNCTION,
45+
typeArguments: [this.assetId],
46+
functionArguments: [AccountAddress.fromString(this.recipients[0].address), this.recipients[0].amount],
47+
};
5248
}
5349

5450
private isValidPayload(payload: TransactionPayload): boolean {
@@ -59,34 +55,4 @@ export class TransferTransaction extends Transaction {
5955
payload.entryFunction.type_args[0].toString().length > 0
6056
);
6157
}
62-
63-
private parseRecipients(addressArg: EntryFunctionArgument, amountArg: EntryFunctionArgument): TransactionRecipient[] {
64-
const { recipients, isValid } = utils.fetchAndValidateRecipients(addressArg, amountArg);
65-
if (!isValid) {
66-
throw new InvalidTransactionError('Invalid transaction recipients');
67-
}
68-
return recipients.deserializedAddresses.map((address, index) => ({
69-
address,
70-
amount: utils.getAmountFromPayloadArgs(recipients.deserializedAmounts[index]),
71-
})) as TransactionRecipient[];
72-
}
73-
74-
private buildData(recipients: TransactionRecipient[]): InputGenerateTransactionPayloadData {
75-
if (recipients.length > 1) {
76-
return {
77-
function: COIN_BATCH_TRANSFER_FUNCTION,
78-
typeArguments: [this.assetId],
79-
functionArguments: [
80-
recipients.map((recipient) => AccountAddress.fromString(recipient.address)),
81-
recipients.map((recipient) => recipient.amount),
82-
],
83-
};
84-
} else {
85-
return {
86-
function: COIN_TRANSFER_FUNCTION,
87-
typeArguments: [this.assetId],
88-
functionArguments: [AccountAddress.fromString(recipients[0].address), recipients[0].amount],
89-
};
90-
}
91-
}
9258
}

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { TransactionBuilder } from './transactionBuilder';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
33
import { FungibleAssetTransfer } from '../transaction/fungibleAssetTransfer';
44
import { TransactionType } from '@bitgo/sdk-core';
5-
import BigNumber from 'bignumber.js';
65
import utils from '../utils';
76
import { TransactionPayload, TransactionPayloadEntryFunction } from '@aptos-labs/ts-sdk';
87
import { FUNGIBLE_ASSET_TYPE_ARGUMENT } from '../constants';
@@ -34,28 +33,30 @@ export class FungibleAssetTransferBuilder extends TransactionBuilder {
3433

3534
protected isValidTransactionPayload(payload: TransactionPayload) {
3635
try {
37-
if (
38-
!(payload instanceof TransactionPayloadEntryFunction) ||
39-
payload.entryFunction.args.length !== 3 ||
40-
payload.entryFunction.type_args.length !== 1 ||
41-
FUNGIBLE_ASSET_TYPE_ARGUMENT !== payload.entryFunction.type_args[0].toString()
42-
) {
36+
if (!this.isValidPayload(payload)) {
4337
console.error('invalid transaction payload');
4438
return false;
4539
}
46-
const entryFunction = payload.entryFunction;
40+
const entryFunction = (payload as TransactionPayloadEntryFunction).entryFunction;
4741
const fungibleTokenAddress = entryFunction.args[0].toString();
48-
const recipientAddress = entryFunction.args[1].toString();
49-
const amountBuffer = Buffer.from(entryFunction.args[2].bcsToBytes());
50-
const recipientAmount = new BigNumber(amountBuffer.readBigUint64LE().toString());
42+
const addressArg = entryFunction.args[1];
43+
const amountArg = entryFunction.args[2];
5144
return (
52-
utils.isValidAddress(recipientAddress) &&
53-
utils.isValidAddress(fungibleTokenAddress) &&
54-
!recipientAmount.isLessThan(0)
45+
utils.isValidAddress(fungibleTokenAddress) && utils.fetchAndValidateRecipients(addressArg, amountArg).isValid
5546
);
5647
} catch (e) {
5748
console.error('invalid transaction payload', e);
5849
return false;
5950
}
6051
}
52+
53+
private isValidPayload(payload: TransactionPayload) {
54+
return (
55+
payload instanceof TransactionPayloadEntryFunction &&
56+
payload.entryFunction.args.length === 3 &&
57+
(payload.entryFunction.type_args.length === 0 ||
58+
(payload.entryFunction.type_args.length === 1 &&
59+
FUNGIBLE_ASSET_TYPE_ARGUMENT === payload.entryFunction.type_args[0].toString()))
60+
);
61+
}
6162
}

0 commit comments

Comments
 (0)