diff --git a/ecosystem/typescript/sdk/CHANGELOG.md b/ecosystem/typescript/sdk/CHANGELOG.md index 6bd735e9fc9e3..151b4ccd06435 100644 --- a/ecosystem/typescript/sdk/CHANGELOG.md +++ b/ecosystem/typescript/sdk/CHANGELOG.md @@ -6,9 +6,12 @@ All notable changes to the Aptos Node SDK will be captured in this file. This ch ## Unreleased -- add missing fields to TokenData class -- add PropertyMap and PropertyValue type to match on-chain data -- support token property map deseralizer to read the property map in the original data format. +- Add missing fields to TokenData class +- Add PropertyMap and PropertyValue type to match on-chain data +- Support token property map deseralizer to read the property map in the original data format. +- Allow `checkBalance` in `CoinClient` to take in a `MaybeHexString` as well as `AptosAccount`, since users might want to check the balance of accounts they don't own (which is generally how you use `AptosAccount`). +- Similar to `checkBalance`, allow `transfer` in `CoinClient` to take in a `MaybeHexString` for the `receiver` argument. +- Add a new `createReceiverIfMissing` argument to `transfer` in `CoinClient`. If set, the `0x1::aptos_account::transfer` function will be called instead of `0x1::coin::transfer`, which will create the account on chain if it doesn't exist instead of failing. ## 1.3.17 (2022-11-08) diff --git a/ecosystem/typescript/sdk/src/abis.ts b/ecosystem/typescript/sdk/src/abis.ts index addd96ffd0767..7e56889b34dfa 100644 --- a/ecosystem/typescript/sdk/src/abis.ts +++ b/ecosystem/typescript/sdk/src/abis.ts @@ -9,9 +9,26 @@ export const TOKEN_TRANSFER_OPT_IN = /* Follow these steps to get the ABI strings: -1. Compile the Move packages with Aptos CLI. e.g. aptos move compile --package-dir ./aptos-move/framework/aptos-token -2. Find the ABI files under the `build` directory and convert the ABI files to hex strings. On Mac and Linux, this can -be done with `cat | od -v -t x1 -A n | tr -d ' \n'` + +Go to the package directory of the relevant Move module, e.g. if you're trying +to get the ABI for the `transfer` function of `aptos_account.move`, go to +the directory `aptos-move/framework/aptos-framework`. + +Compile the Move packages with the Aptos CLI: +``` +aptos move compile --included-artifacts all +``` +This `--included-artifacts all` argument is necessary to generate ABIs. + +Find the ABI files under the `build` directory and convert the ABI files to hex strings. +On Mac and Linux, this can be done with this command: +``` +cat | od -v -t x1 -A n | tr -d ' \n' +``` +For example: +``` +cat build/AptosFramework/abis/aptos_account/transfer.abi | od -v -t x1 -A n | tr -d ' \n' +``` */ export const TOKEN_ABIS = [ // aptos-token/build/AptosToken/abis/token/create_collection_script.abi @@ -39,4 +56,6 @@ export const TOKEN_ABIS = [ export const COIN_ABIS = [ // aptos-framework/build/AptosFramework/abis/coin/transfer.abi "01087472616E73666572000000000000000000000000000000000000000000000000000000000000000104636F696E3C205472616E73666572732060616D6F756E7460206F6620636F696E732060436F696E54797065602066726F6D206066726F6D6020746F2060746F602E0109636F696E5F747970650202746F0406616D6F756E7402", + // aptos-framework/build/AptosFramework/abis/aptos_account/transfer.abi + "01087472616e7366657200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e7400000202746f0406616d6f756e7402", ]; diff --git a/ecosystem/typescript/sdk/src/aptos_account.test.ts b/ecosystem/typescript/sdk/src/aptos_account.test.ts index dda1ab47a1868..a7b08280bb1c0 100644 --- a/ecosystem/typescript/sdk/src/aptos_account.test.ts +++ b/ecosystem/typescript/sdk/src/aptos_account.test.ts @@ -1,7 +1,8 @@ // Copyright (c) Aptos // SPDX-License-Identifier: Apache-2.0 -import { AptosAccount, AptosAccountObject } from "./aptos_account"; +import { AptosAccount, AptosAccountObject, getAddressFromAccountOrAddress } from "./aptos_account"; +import { HexString } from "./hex_string"; const aptosAccountObject: AptosAccountObject = { address: "0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa", @@ -74,3 +75,12 @@ test("Gets the resource account address", () => { "0xcbed05b37b6981a57f535c1f5d136734df822abaf4cd30c51c9b4d60eae79d5d", ); }); + +test("Test getAddressFromAccountOrAddress", () => { + const account = AptosAccount.fromAptosAccountObject(aptosAccountObject); + expect(getAddressFromAccountOrAddress(aptosAccountObject.address!).toString()).toBe(aptosAccountObject.address); + expect(getAddressFromAccountOrAddress(HexString.ensure(aptosAccountObject.address!)).toString()).toBe( + aptosAccountObject.address, + ); + expect(getAddressFromAccountOrAddress(account).toString()).toBe(aptosAccountObject.address); +}); diff --git a/ecosystem/typescript/sdk/src/aptos_account.ts b/ecosystem/typescript/sdk/src/aptos_account.ts index 4d562d36c1a09..1d61b64833096 100644 --- a/ecosystem/typescript/sdk/src/aptos_account.ts +++ b/ecosystem/typescript/sdk/src/aptos_account.ts @@ -177,3 +177,8 @@ export class AptosAccount { }; } } + +// Returns an account address as a HexString given either an AptosAccount or a MaybeHexString. +export function getAddressFromAccountOrAddress(accountOrAddress: AptosAccount | MaybeHexString): HexString { + return accountOrAddress instanceof AptosAccount ? accountOrAddress.address() : HexString.ensure(accountOrAddress); +} diff --git a/ecosystem/typescript/sdk/src/coin_client.test.ts b/ecosystem/typescript/sdk/src/coin_client.test.ts index f5365730c453c..7ed7be4e3b027 100644 --- a/ecosystem/typescript/sdk/src/coin_client.test.ts +++ b/ecosystem/typescript/sdk/src/coin_client.test.ts @@ -7,7 +7,7 @@ import { AptosAccount } from "./aptos_account"; import { CoinClient } from "./coin_client"; test( - "transferCoins and checkBalance works", + "transfer and checkBalance works", async () => { const client = new AptosClient(NODE_URL); const faucetClient = getFaucetClient(); @@ -21,6 +21,15 @@ test( await client.waitForTransaction(await coinClient.transfer(alice, bob, 42), { checkSuccess: true }); expect(await coinClient.checkBalance(bob)).toBe(BigInt(42)); + + // Test that `createReceiverIfMissing` works. + const jemima = new AptosAccount(); + await client.waitForTransaction(await coinClient.transfer(alice, jemima, 717, { createReceiverIfMissing: true }), { + checkSuccess: true, + }); + + // Check that using a string address instead of an account works with `checkBalance`. + expect(await coinClient.checkBalance(jemima.address().hex())).toBe(BigInt(717)); }, longTestTimeout, ); diff --git a/ecosystem/typescript/sdk/src/coin_client.ts b/ecosystem/typescript/sdk/src/coin_client.ts index a4059880eabb2..272267bd90474 100644 --- a/ecosystem/typescript/sdk/src/coin_client.ts +++ b/ecosystem/typescript/sdk/src/coin_client.ts @@ -1,9 +1,9 @@ // Copyright (c) Aptos // SPDX-License-Identifier: Apache-2.0 -import { AptosAccount } from "./aptos_account"; +import { AptosAccount, getAddressFromAccountOrAddress } from "./aptos_account"; import { AptosClient, OptionalTransactionArgs } from "./aptos_client"; -import { HexString } from "./hex_string"; +import { HexString, MaybeHexString } from "./hex_string"; import { TransactionBuilderABI } from "./transaction_builder"; import { COIN_ABIS } from "./abis"; import { APTOS_COIN } from "./utils"; @@ -28,7 +28,14 @@ export class CoinClient { /** * Generate, sign, and submit a transaction to the Aptos blockchain API to - * transfer AptosCoin from one account to another. + * transfer coins from one account to another. By default it transfers + * 0x1::aptos_coin::AptosCoin, but you can specify a different coin type + * with the `coinType` argument. + * + * You may set `createReceiverIfMissing` to true if you want to create the + * receiver account if it does not exist on chain yet. If you do not set + * this to true, the transaction will fail if the receiver account does not + * exist on-chain. * * @param from Account sending the coins * @param to Account to receive the coins @@ -40,36 +47,48 @@ export class CoinClient { // :!:>transfer async transfer( from: AptosAccount, - to: AptosAccount, + to: AptosAccount | MaybeHexString, amount: number | bigint, extraArgs?: OptionalTransactionArgs & { // The coin type to use, defaults to 0x1::aptos_coin::AptosCoin coinType?: string; + // 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 + // transferring the coins to it. + createReceiverIfMissing?: boolean; }, ): Promise { + // If none is explicitly given, use 0x1::aptos_coin::AptosCoin as the coin type. const coinTypeToTransfer = extraArgs?.coinType ?? APTOS_COIN; - const payload = this.transactionBuilder.buildTransactionPayload( - "0x1::coin::transfer", - [coinTypeToTransfer], - [to.address(), amount], - ); + + // If we should create the receiver account if it doesn't exist on-chain, + // use the `0x1::aptos_account::transfer` function. + const func = extraArgs?.createReceiverIfMissing ? "0x1::aptos_account::transfer" : "0x1::coin::transfer"; + + // If we're using the `0x1::aptos_account::transfer` function, we don't + // need type args. + const typeArgs = extraArgs?.createReceiverIfMissing ? [] : [coinTypeToTransfer]; + + // Get the receiver address from the AptosAccount or MaybeHexString. + const toAddress = getAddressFromAccountOrAddress(to); + + const payload = this.transactionBuilder.buildTransactionPayload(func, typeArgs, [toAddress, amount]); + return this.aptosClient.generateSignSubmitTransaction(from, payload, extraArgs); } // <:!:transfer /** - * Generate, submit, and wait for a transaction to transfer AptosCoin from - * one account to another. - * - * If the transaction is submitted successfully, it returns the response - * from the API indicating that the transaction was submitted. + * 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. * - * @param account Account that you want to check the balance of. + * @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. */ // :!:>checkBalance async checkBalance( - account: AptosAccount, + account: AptosAccount | MaybeHexString, extraArgs?: { // The coin type to use, defaults to 0x1::aptos_coin::AptosCoin coinType?: string; @@ -77,7 +96,8 @@ export class CoinClient { ): Promise { const coinType = extraArgs?.coinType ?? APTOS_COIN; const typeTag = `0x1::coin::CoinStore<${coinType}>`; - const resources = await this.aptosClient.getAccountResources(account.address()); + const address = getAddressFromAccountOrAddress(account); + const resources = await this.aptosClient.getAccountResources(address); const accountResource = resources.find((r) => r.type === typeTag); return BigInt((accountResource!.data as any).coin.value); } // <:!:checkBalance