Skip to content

Commit

Permalink
[TS SDK] Support write operations for Fungible Assets (#8294)
Browse files Browse the repository at this point in the history
* add token v2 support

* support aptos token

* support aptos token

* add functionalities support

* get token address from txn events

* get token address from txn events

* get token address from txn events

* update changelog

* move fungiable asset yransfer method to fungible asset client class

* add support for fungible assets

* change console warn message

* add tests for fungible asset client

* support fungible asset in coin client

* modify changelog

* revert unneeded changes

* update comments

* reorganize

* reorganize

* fix lint

* address feedback

* remove type argument parameters

* support get balance for any fungible store

* Revert "support get balance for any fungible store"

This reverts commit a2dd855.

* support fa in coin client class

* support fa in coin client class

* support fa in coin client class

* fix lint

* comments

* update changelog

* update changelog

* rename functions
  • Loading branch information
0xmaayan authored and banool committed Jul 7, 2023
1 parent bba32aa commit a02c4ae
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 7 deletions.
2 changes: 2 additions & 0 deletions ecosystem/typescript/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ All notable changes to the Aptos Node SDK will be captured in this file. This ch
- Change `indexerUrl` param on `Provider` class to an optional parameter
- Add `getCollectionsWithOwnedTokens` query to fetch all collections that an account has tokens for
- Support `tokenStandard` param in `getOwnedTokens` and `getTokenOwnedFromCollectionAddress` queries
- Add `FungibleAssetClient` plugin to support fungible assets
- Support fungible assets in `CoinClient` class operations

## 1.9.1 (2023-05-24)

Expand Down
62 changes: 56 additions & 6 deletions ecosystem/typescript/sdk/src/plugins/coin_client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

import { AptosAccount, getAddressFromAccountOrAddress } from "../account/aptos_account";
import { AptosClient, OptionalTransactionArgs } from "../providers/aptos_client";
import { MaybeHexString, APTOS_COIN } from "../utils";
import { MaybeHexString, APTOS_COIN, NetworkToIndexerAPI, NodeAPIToNetwork } from "../utils";
import { TransactionBuilderRemoteABI } from "../transaction_builder";
import { FungibleAssetClient } from "./fungible_asset_client";
import { Provider } from "../providers";
import { AccountAddress } from "../aptos_types";

/**
* Class for working with the coin module, such as transferring coins and
Expand Down Expand Up @@ -32,6 +34,11 @@ export class CoinClient {
* this to true, the transaction will fail if the receiver account does not
* exist on-chain.
*
* The TS SDK supports fungible assets operations. If you want to use CoinClient
* with this feature, set the `coinType` to be the fungible asset metadata address.
* This option uses the `FungibleAssetClient` class and queries the
* fungible asset primary store.
*
* @param from Account sending the coins
* @param to Account to receive the coins
* @param amount Number of coins to transfer
Expand All @@ -45,8 +52,10 @@ export class CoinClient {
to: AptosAccount | MaybeHexString,
amount: number | bigint,
extraArgs?: OptionalTransactionArgs & {
// The coin type to use, defaults to 0x1::aptos_coin::AptosCoin
coinType?: string;
// The coin type to use, defaults to 0x1::aptos_coin::AptosCoin.
// If you want to transfer a fungible asset, set this param to be the
// fungible asset address
coinType?: string | MaybeHexString;
// If set, create the `receiver` account if it doesn't exist on-chain.
// This is done by calling `0x1::aptos_account::transfer` instead, which
// will create the account on-chain first if it doesn't exist before
Expand All @@ -56,6 +65,23 @@ export class CoinClient {
createReceiverIfMissing?: boolean;
},
): Promise<string> {
if (extraArgs?.coinType && AccountAddress.isValid(extraArgs.coinType)) {
/* eslint-disable no-console */
console.warn("to transfer a fungible asset, use `FungibleAssetClient()` class for better support");
const provider = new Provider({
fullnodeUrl: this.aptosClient.nodeUrl,
indexerUrl: NetworkToIndexerAPI[NodeAPIToNetwork[this.aptosClient.nodeUrl]] ?? this.aptosClient.nodeUrl,
});
const fungibleAsset = new FungibleAssetClient(provider);
const txnHash = await fungibleAsset.transfer(
from,
extraArgs?.coinType,
getAddressFromAccountOrAddress(to),
amount,
);
return txnHash;
}

// If none is explicitly given, use 0x1::aptos_coin::AptosCoin as the coin type.
const coinTypeToTransfer = extraArgs?.coinType ?? APTOS_COIN;

Expand All @@ -67,7 +93,7 @@ export class CoinClient {
const toAddress = getAddressFromAccountOrAddress(to);

const builder = new TransactionBuilderRemoteABI(this.aptosClient, { sender: from.address(), ...extraArgs });
const rawTxn = await builder.build(func, [coinTypeToTransfer], [toAddress, amount]);
const rawTxn = await builder.build(func, [coinTypeToTransfer as string], [toAddress, amount]);

const bcsTxn = AptosClient.generateBCSTransaction(from, rawTxn);
const pendingTransaction = await this.aptosClient.submitSignedBCSTransaction(bcsTxn);
Expand All @@ -78,6 +104,13 @@ export class CoinClient {
* Get the balance of the account. By default it checks the balance of
* 0x1::aptos_coin::AptosCoin, but you can specify a different coin type.
*
* to use a different type, set the `coinType` to be the fungible asset type.
*
* The TS SDK supports fungible assets operations. If you want to use CoinClient
* with this feature, set the `coinType` to be the fungible asset metadata address.
* This option uses the FungibleAssetClient class and queries the
* fungible asset primary store.
*
* @param account Account that you want to get the balance of.
* @param extraArgs Extra args for checking the balance.
* @returns Promise that resolves to the balance as a bigint.
Expand All @@ -86,10 +119,27 @@ export class CoinClient {
async checkBalance(
account: AptosAccount | MaybeHexString,
extraArgs?: {
// The coin type to use, defaults to 0x1::aptos_coin::AptosCoin
// The coin type to use, defaults to 0x1::aptos_coin::AptosCoin.
// If you want to check the balance of a fungible asset, set this param to be the
// fungible asset address
coinType?: string;
},
): Promise<bigint> {
if (extraArgs?.coinType && AccountAddress.isValid(extraArgs.coinType)) {
/* eslint-disable no-console */
console.warn("to check balance of a fungible asset, use `FungibleAssetClient()` class for better support");
const provider = new Provider({
fullnodeUrl: this.aptosClient.nodeUrl,
indexerUrl: NetworkToIndexerAPI[NodeAPIToNetwork[this.aptosClient.nodeUrl]] ?? this.aptosClient.nodeUrl,
});
const fungibleAsset = new FungibleAssetClient(provider);
const balance = await fungibleAsset.getPrimaryBalance(
getAddressFromAccountOrAddress(account),
extraArgs?.coinType,
);
return balance;
}

const coinType = extraArgs?.coinType ?? APTOS_COIN;
const typeTag = `0x1::coin::CoinStore<${coinType}>`;
const address = getAddressFromAccountOrAddress(account);
Expand Down
102 changes: 102 additions & 0 deletions ecosystem/typescript/sdk/src/plugins/fungible_asset_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { AptosAccount } from "../account";
import { RawTransaction } from "../aptos_types";
import * as Gen from "../generated/index";
import { OptionalTransactionArgs, Provider } from "../providers";
import { TransactionBuilderRemoteABI } from "../transaction_builder";
import { MaybeHexString, HexString } from "../utils";

export class FungibleAssetClient {
provider: Provider;

readonly assetType: string = "0x1::fungible_asset::Metadata";

/**
* Creates new FungibleAssetClient instance
*
* @param provider Provider instance
*/
constructor(provider: Provider) {
this.provider = provider;
}

/**
* Transfer `amount` of fungible asset from sender's primary store to recipient's primary store.
*
* Use this method to transfer any fungible asset including fungible token.
*
* @param sender The sender account
* @param fungibleAssetMetadataAddress The fungible asset address.
* For example if you’re transferring USDT this would be the USDT address
* @param recipient Recipient address
* @param amount Number of assets to transfer
* @returns The hash of the transaction submitted to the API
*/
async transfer(
sender: AptosAccount,
fungibleAssetMetadataAddress: MaybeHexString,
recipient: MaybeHexString,
amount: number | bigint,
extraArgs?: OptionalTransactionArgs,
): Promise<string> {
const rawTransaction = await this.generateTransfer(
sender,
fungibleAssetMetadataAddress,
recipient,
amount,
extraArgs,
);
const txnHash = await this.provider.signAndSubmitTransaction(sender, rawTransaction);
return txnHash;
}

/**
* Get the balance of a fungible asset from the account's primary fungible store.
*
* @param account Account that you want to get the balance of.
* @param fungibleAssetMetadataAddress The fungible asset address you want to check the balance of
* @returns Promise that resolves to the balance
*/
async getPrimaryBalance(account: MaybeHexString, fungibleAssetMetadataAddress: MaybeHexString): Promise<bigint> {
const payload: Gen.ViewRequest = {
function: "0x1::primary_fungible_store::balance",
type_arguments: [this.assetType],
arguments: [HexString.ensure(account).hex(), HexString.ensure(fungibleAssetMetadataAddress).hex()],
};
const response = await this.provider.view(payload);
return BigInt((response as any)[0]);
}

/**
*
* Generate a transfer transaction that can be used to sign and submit to transfer an asset amount
* from the sender primary fungible store to the recipient primary fungible store.
*
* This method can be used if you want/need to get the raw transaction so you can
* first simulate the transaction and then sign and submit it.
*
* @param sender The sender account
* @param fungibleAssetMetadataAddress The fungible asset address.
* For example if you’re transferring USDT this would be the USDT address
* @param recipient Recipient address
* @param amount Number of assets to transfer
* @returns Raw Transaction
*/
async generateTransfer(
sender: AptosAccount,
fungibleAssetMetadataAddress: MaybeHexString,
recipient: MaybeHexString,
amount: number | bigint,
extraArgs?: OptionalTransactionArgs,
): Promise<RawTransaction> {
const builder = new TransactionBuilderRemoteABI(this.provider, {
sender: sender.address(),
...extraArgs,
});
const rawTxn = await builder.build(
"0x1::primary_fungible_store::transfer",
[this.assetType],
[HexString.ensure(fungibleAssetMetadataAddress).hex(), HexString.ensure(recipient).hex(), amount],
);
return rawTxn;
}
}
1 change: 1 addition & 0 deletions ecosystem/typescript/sdk/src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./aptos_token";
export * from "./coin_client";
export * from "./faucet_client";
export * from "./ans_client";
export * from "./fungible_asset_client";
15 changes: 14 additions & 1 deletion ecosystem/typescript/sdk/src/providers/aptos_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
Uint64,
AnyNumber,
} from "../bcs";
import { Ed25519PublicKey, MultiEd25519PublicKey } from "../aptos_types";
import { Ed25519PublicKey, MultiEd25519PublicKey, RawTransaction } from "../aptos_types";

export interface OptionalTransactionArgs {
maxGasAmount?: Uint64;
Expand Down Expand Up @@ -742,6 +742,19 @@ export class AptosClient {
// <:!:generateSignSubmitTransactionInner
}

/**
* Helper for signing and submitting a transaction.
*
* @param sender AptosAccount of transaction sender.
* @param transaction A generated Raw transaction payload.
* @returns The transaction response from the API.
*/
async signAndSubmitTransaction(sender: AptosAccount, transaction: RawTransaction): Promise<string> {
const bcsTxn = AptosClient.generateBCSTransaction(sender, transaction);
const pendingTransaction = await this.submitSignedBCSTransaction(bcsTxn);
return pendingTransaction.hash;
}

/**
* Publishes a move package. `packageMetadata` and `modules` can be generated with command
* `aptos move compile --save-metadata [ --included-artifacts=<...> ]`.
Expand Down
Loading

0 comments on commit a02c4ae

Please sign in to comment.