Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk-coin-apt): non fungible assest transfer #5440

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions modules/sdk-coin-apt/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ export const UNAVAILABLE_TEXT = 'UNAVAILABLE';
export const DEFAULT_GAS_UNIT_PRICE = 100;
export const SECONDS_PER_WEEK = 7 * 24 * 60 * 60; // Days * Hours * Minutes * Seconds

export const APTOS_ACCOUNT_MODULE = 'aptos_account';
export const FUNGIBLE_ASSET_MODULE = 'primary_fungible_store';
export const DIGITAL_ASSET_TRANSFER_AMOUNT = '1';

export const FUNGIBLE_ASSET_TRANSFER_FUNCTION = '0x1::primary_fungible_store::transfer';
export const COIN_TRANSFER_FUNCTION = '0x1::aptos_account::transfer_coins';
export const DIGITAL_ASSET_TRANSFER_FUNCTION = '0x1::object::transfer';

export const FUNGIBLE_ASSET_TYPE_ARGUMENT = '0x1::fungible_asset::Metadata';
export const APTOS_COIN = '0x1::aptos_coin::AptosCoin';
export const FUNGIBLE_ASSET_TYPE_ARGUMENT = '0x1::fungible_asset::Metadata';
export const DIGITAL_ASSET_TYPE_ARGUMENT = '0x4::token::Token';
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Transaction } from './transaction';
bhavidhingra marked this conversation as resolved.
Show resolved Hide resolved
import {
AccountAddress,
Aptos,
AptosConfig,
EntryFunctionABI,
MoveAbility,
Network,
objectStructTag,
TransactionPayload,
TransactionPayloadEntryFunction,
TypeTagAddress,
TypeTagGeneric,
TypeTagStruct,
} from '@aptos-labs/ts-sdk';
import { InvalidTransactionError, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';
import {
DIGITAL_ASSET_TYPE_ARGUMENT,
DIGITAL_ASSET_TRANSFER_FUNCTION,
DIGITAL_ASSET_TRANSFER_AMOUNT,
} from '../constants';

export class DigitalAssetTransaction extends Transaction {
constructor(coinConfig: Readonly<CoinConfig>) {
super(coinConfig);
this._type = TransactionType.SendNFT;
}

protected parseTransactionPayload(payload: TransactionPayload): void {
if (
!(payload instanceof TransactionPayloadEntryFunction) ||
payload.entryFunction.args.length !== 2 ||
payload.entryFunction.type_args.length !== 1 ||
DIGITAL_ASSET_TYPE_ARGUMENT !== payload.entryFunction.type_args[0].toString()
) {
throw new InvalidTransactionError('Invalid transaction payload');
}
const entryFunction = payload.entryFunction;
if (!this._recipient) {
this._recipient = {} as TransactionRecipient;
}
this._assetId = entryFunction.args[0].toString();
this._recipient.address = entryFunction.args[1].toString();
this._recipient.amount = DIGITAL_ASSET_TRANSFER_AMOUNT;
}

protected async buildRawTransaction(): Promise<void> {
const network: Network = this._coinConfig.network.type === NetworkType.MAINNET ? Network.MAINNET : Network.TESTNET;
const aptos = new Aptos(new AptosConfig({ network }));
const senderAddress = AccountAddress.fromString(this._sender);
const recipientAddress = AccountAddress.fromString(this._recipient.address);
const digitalAssetAddress = AccountAddress.fromString(this._assetId);

const transferDigitalAssetAbi: EntryFunctionABI = {
typeParameters: [{ constraints: [MoveAbility.KEY] }],
parameters: [new TypeTagStruct(objectStructTag(new TypeTagGeneric(0))), new TypeTagAddress()],
};

const simpleTxn = await aptos.transaction.build.simple({
sender: senderAddress,
data: {
function: DIGITAL_ASSET_TRANSFER_FUNCTION,
typeArguments: [DIGITAL_ASSET_TYPE_ARGUMENT],
functionArguments: [digitalAssetAddress, recipientAddress],
abi: transferDigitalAssetAbi,
},
options: {
maxGasAmount: this.maxGasAmount,
gasUnitPrice: this.gasUnitPrice,
expireTimestamp: this.expirationTime,
accountSequenceNumber: this.sequenceNumber,
},
});
this._rawTransaction = simpleTxn.rawTransaction;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { TransactionBuilder } from './transactionBuilder';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { TransactionType } from '@bitgo/sdk-core';
import utils from '../utils';
import { TransactionPayload, TransactionPayloadEntryFunction } from '@aptos-labs/ts-sdk';
import { DigitalAssetTransaction } from '../transaction/digitalAssetTransaction';
import { DIGITAL_ASSET_TYPE_ARGUMENT } from '../constants';

export class DigitalAssetTransactionBuilder extends TransactionBuilder {
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._transaction = new DigitalAssetTransaction(_coinConfig);
}

protected get transactionType(): TransactionType {
return TransactionType.SendNFT;
}

assetId(assetId: string): TransactionBuilder {
this.validateAddress({ address: assetId });
this.transaction.assetId = assetId;
return this;
}

/** @inheritdoc */
validateTransaction(transaction?: DigitalAssetTransaction): void {
if (!transaction) {
bhavidhingra marked this conversation as resolved.
Show resolved Hide resolved
throw new Error('transaction not defined');
}
super.validateTransaction(transaction);
this.validateAddress({ address: transaction.assetId });
}

protected isValidTransactionPayload(payload: TransactionPayload) {
try {
if (
!(payload instanceof TransactionPayloadEntryFunction) ||
payload.entryFunction.args.length !== 2 ||
payload.entryFunction.type_args.length !== 1 ||
DIGITAL_ASSET_TYPE_ARGUMENT !== payload.entryFunction.type_args[0].toString()
) {
console.error('invalid transaction payload');
return false;
}
const entryFunction = payload.entryFunction;
const digitalAssetAddress = entryFunction.args[0].toString();
const recipientAddress = entryFunction.args[1].toString();
return utils.isValidAddress(recipientAddress) && utils.isValidAddress(digitalAssetAddress);
} catch (e) {
console.error('invalid transaction payload', e);
return false;
}
}
}
11 changes: 11 additions & 0 deletions modules/sdk-coin-apt/src/lib/transactionBuilderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { TransferTransaction } from './transaction/transferTransaction';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { FungibleAssetTransaction } from './transaction/fungibleAssetTransaction';
import { FungibleAssetTransactionBuilder } from './transactionBuilder/fungibleAssetTransactionBuilder';
import { DigitalAssetTransaction } from './transaction/digitalAssetTransaction';
import { DigitalAssetTransactionBuilder } from './transactionBuilder/digitalAssestTransactionBuilder';

export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
constructor(_coinConfig: Readonly<CoinConfig>) {
Expand All @@ -28,6 +30,10 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
const fungibleTransferTokenTx = new FungibleAssetTransaction(this._coinConfig);
fungibleTransferTokenTx.fromDeserializedSignedTransaction(signedTxn);
return this.getFungibleAssetTransactionBuilder(fungibleTransferTokenTx);
case TransactionType.SendNFT:
const digitalAssetTransferTx = new DigitalAssetTransaction(this._coinConfig);
digitalAssetTransferTx.fromDeserializedSignedTransaction(signedTxn);
return this.getDigitalAssetTransactionBuilder(digitalAssetTransferTx);
default:
throw new InvalidTransactionError('Invalid transaction');
}
Expand All @@ -51,6 +57,11 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
return this.initializeBuilder(tx, new FungibleAssetTransactionBuilder(this._coinConfig));
}

/** @inheritdoc */
getDigitalAssetTransactionBuilder(tx?: Transaction): DigitalAssetTransactionBuilder {
return this.initializeBuilder(tx, new DigitalAssetTransactionBuilder(this._coinConfig));
}

/** @inheritdoc */
getWalletInitializationBuilder(): void {
throw new Error('Method not implemented.');
Expand Down
16 changes: 11 additions & 5 deletions modules/sdk-coin-apt/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import {
APT_BLOCK_ID_LENGTH,
APT_SIGNATURE_LENGTH,
APT_TRANSACTION_ID_LENGTH,
APTOS_ACCOUNT_MODULE,
FUNGIBLE_ASSET_MODULE,
COIN_TRANSFER_FUNCTION,
DIGITAL_ASSET_TRANSFER_FUNCTION,
FUNGIBLE_ASSET_TRANSFER_FUNCTION,
} from './constants';
import BigNumber from 'bignumber.js';

Expand Down Expand Up @@ -72,12 +73,17 @@ export class Utils implements BaseUtils {
throw new Error('Invalid Payload: Expected TransactionPayloadEntryFunction');
}
const entryFunction = payload.entryFunction;
const moduleAddress = entryFunction.module_name.address.toString();
const moduleIdentifier = entryFunction.module_name.name.identifier;
switch (moduleIdentifier) {
case APTOS_ACCOUNT_MODULE:
const functionIdentifier = entryFunction.function_name.identifier;
const uniqueIdentifier = `${moduleAddress}::${moduleIdentifier}::${functionIdentifier}`;
switch (uniqueIdentifier) {
case COIN_TRANSFER_FUNCTION:
return TransactionType.Send;
case FUNGIBLE_ASSET_MODULE:
case FUNGIBLE_ASSET_TRANSFER_FUNCTION:
return TransactionType.SendToken;
case DIGITAL_ASSET_TRANSFER_FUNCTION:
return TransactionType.SendNFT;
default:
throw new InvalidTransactionError(`Invalid transaction: unable to fetch transaction type ${moduleIdentifier}`);
}
Expand Down
16 changes: 14 additions & 2 deletions modules/sdk-coin-apt/test/resources/apt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ export const fungibleTokenRecipients: Recipient[] = [
},
];

export const digitalTokenRecipients: Recipient[] = [
{
address: addresses.validAddresses[0],
amount: '1',
},
];

export const invalidRecipients: Recipient[] = [
{
address: addresses.invalidAddresses[0],
Expand All @@ -67,14 +74,19 @@ export const TRANSFER =
export const TRANSACTION_USING_TRANSFER_COINS =
'0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a37244992000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e73010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d0300000000006400000000000000979390670000000002030020f73836f42257240e43d439552471fc9dbcc3f1af5bd0b4ed83f44b5f6614644240caeb90efd4b7ecd922c97bb3163e6a9de1fbb2ee0fc0d56af484f4af9b0015c5831341550af29b3686713b6657c821d894635fe13c7933f06ee043728f040b090000dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f200205223396c531f13e031a9f0cb26d459d799a52e51be9a1cb9e871afb4c31f91ff4013e7e8a1325ee5f656c93baa3d0206a1d9bd6da5abdc6f5d9b8bbbb0926ddac68f3e57a915dd217d2d43e776a6cc01af72f895ea712acc836d30349f29a3c606';

export const FUNGIBLE_TOKEN_TRANSFER =
'0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449a700000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d65746164617461000320d5d0d561493ea2b9410f67da804653ae44e793c2423707d4f11edb2e3819205020f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad9080100000000000000400d0300000000006400000000000000e42696670000000002030020f73836f42257240e43d439552471fc9dbcc3f1af5bd0b4ed83f44b5f661464424029665cd4c94658a0d83907bbed7e761794b25bccc03fc87e6dd63a543accdddfd7a6f1e7a15e8681547ca437ff99b58c92f816e35a0f333d7f1fd1330c21ad0a0000dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f200205223396c531f13e031a9f0cb26d459d799a52e51be9a1cb9e871afb4c31f91ff40de7b0bb86ca346031942e9cf21ff9604c7c08c2c662e38a0afe3dd640512c0441396c0697cd8bbbcf39694d6f88e35f6bed9fb34bd209b0479b5e8bd0cf3eb0b';

export const DIGITAL_ASSET_TRANSFER =
'0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449ab00000000000000020000000000000000000000000000000000000000000000000000000000000001066f626a656374087472616e736665720107000000000000000000000000000000000000000000000000000000000000000405746f6b656e05546f6b656e0002202e356062777469d39ca5d9b72512ce2d5713d7938ed6ca9193d4fc2016a819fd20f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad9400d0300000000006400000000000000526798670000000002030020f73836f42257240e43d439552471fc9dbcc3f1af5bd0b4ed83f44b5f66146442400c2c31eae495d22d1d31031b9756e8238a3df6e760515aa3e1108d93b3aec6aeb91684307e8365ad9dc44c0b5957e95a21a6f47d8d4a6e0eb3b145fb3d517f030000dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f200205223396c531f13e031a9f0cb26d459d799a52e51be9a1cb9e871afb4c31f91ff40dd6c064e44642819dec2f63d32d6daa4f889d62d06025ad99b42562c4c6cdef8b1437739115d7f38050078829efb9dd05528f53309bcab5b89fadb283423100d';

export const INVALID_TRANSFER =
'AAAAAAAAAAAAA6e7361637469bc4a58e500b9e64cb6547ee9b403000000000000002064ba1fb2f2fbd2938a350015d601f4db89cd7e8e2370d0dd9ae3ac4f635c1581111b8a49f67370bc4a58e500b9e64cb6462e39b802000000000000002064ba1fb2f2fbd2938a350015d601f4db89cd7e8e2370d0dd9ae3ac47aa1ff81f01c4173a804406a365e69dfb297d4eaaf002546ebd016400000000000000cba4a48bb0f8b586c167e5dcefaa1c5e96ab3f0836d6ca08f2081732944d1e5b6b406a4a462e39b8030000000000000020b9490ede63215262c434e03f606d9799f3ba704523ceda184b386d47aa1ff81f01000000000000006400000000000000';

export const fungibleTokenAddress = {
usdt: '0xd5d0d561493ea2b9410f67da804653ae44e793c2423707d4f11edb2e38192050',
};

export const FUNGIBLE_TOKEN_TRANSFER =
'0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449a700000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d65746164617461000320d5d0d561493ea2b9410f67da804653ae44e793c2423707d4f11edb2e3819205020f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad9080100000000000000400d0300000000006400000000000000e42696670000000002030020f73836f42257240e43d439552471fc9dbcc3f1af5bd0b4ed83f44b5f661464424029665cd4c94658a0d83907bbed7e761794b25bccc03fc87e6dd63a543accdddfd7a6f1e7a15e8681547ca437ff99b58c92f816e35a0f333d7f1fd1330c21ad0a0000dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f200205223396c531f13e031a9f0cb26d459d799a52e51be9a1cb9e871afb4c31f91ff40de7b0bb86ca346031942e9cf21ff9604c7c08c2c662e38a0afe3dd640512c0441396c0697cd8bbbcf39694d6f88e35f6bed9fb34bd209b0479b5e8bd0cf3eb0b';
export const digitalAssetAddress = '0x2e356062777469d39ca5d9b72512ce2d5713d7938ed6ca9193d4fc2016a819fd';

export const LEGACY_COIN = '0x4fb379c10c763a13e724064ecfb7d946690bea519ba982c81b518d1c11dd23fe::fa_test::Coinz';
4 changes: 2 additions & 2 deletions modules/sdk-coin-apt/test/unit/apt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('APT:', function () {
let newTxParams;

const txPreBuild = {
txHex: testData.TRANSFER,
txHex: testData.TRANSACTION_USING_TRANSFER_COINS,
txInfo: {},
};

Expand Down Expand Up @@ -118,7 +118,7 @@ describe('APT:', function () {

it('should parse a transfer transaction', async function () {
const parsedTransaction = await basecoin.parseTransaction({
txHex: testData.TRANSFER,
txHex: testData.TRANSACTION_USING_TRANSFER_COINS,
});

parsedTransaction.should.deepEqual({
Expand Down
Loading
Loading