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

Add built-in transferCoinTransaction() function #20

Merged
merged 2 commits into from
Oct 12, 2023
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
4 changes: 2 additions & 2 deletions src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,10 @@ export class Account {
}

/**
* Queries the count of an account's coins
* Queries the count of an account's coins aggregated
*
* @param accountAddress The account address we want to get the total count for
* @returns An object { count : number }
* @returns An object { count : number } where `number` is the aggregated count of all account's coin
*/
async getAccountCoinsCount(args: { accountAddress: HexInput }): Promise<GetAccountCoinsCountResponse> {
const count = getAccountCoinsCount({ aptosConfig: this.config, ...args });
Comment on lines 301 to 308
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this query may be confusing. Is it supposed to be the number of coin types? Or combined between multiple coin types?

Let's be concrete

If I have 5 DogCoin and 7 CatCoin, would the number be:

  • 2 (the number of types of coins I have)
  • 12 (the aggregate number between both coins, which isn't really a useful value)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an existing query in sdk v1. I believe it returns the former. can validate it in a second PR as it is not really relevant to this PR

Expand Down
7 changes: 6 additions & 1 deletion src/api/aptos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { Account } from "./account";
import { AptosConfig } from "./aptos_config";
import { Coin } from "./coin";
import { Faucet } from "./faucet";
import { General } from "./general";
import { Transaction } from "./transaction";
Expand All @@ -13,6 +14,8 @@ export class Aptos {

readonly account: Account;

readonly coin: Coin;

readonly faucet: Faucet;

readonly general: General;
Expand Down Expand Up @@ -41,14 +44,15 @@ export class Aptos {
constructor(settings?: AptosConfig) {
this.config = new AptosConfig(settings);
this.account = new Account(this.config);
this.coin = new Coin(this.config);
this.faucet = new Faucet(this.config);
this.general = new General(this.config);
this.transaction = new Transaction(this.config);
this.transactionSubmission = new TransactionSubmission(this.config);
}
}

export interface Aptos extends Account, Faucet, General, Transaction, TransactionSubmission {}
export interface Aptos extends Account, Coin, Faucet, General, Transaction, TransactionSubmission {}

/**
In TypeScript, we can’t inherit or extend from more than one class,
Expand All @@ -72,6 +76,7 @@ function applyMixin(targetClass: any, baseClass: any, baseClassProp: string) {
}

applyMixin(Aptos, Account, "account");
applyMixin(Aptos, Coin, "coin");
applyMixin(Aptos, Faucet, "faucet");
applyMixin(Aptos, General, "general");
applyMixin(Aptos, Transaction, "transaction");
Expand Down
40 changes: 40 additions & 0 deletions src/api/coin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

import { Account } from "../core";
import { transferCoinTransaction } from "../internal/coin";
import { SingleSignerTransaction, GenerateTransactionOptions } from "../transactions/types";
import { AnyNumber, HexInput, MoveResourceType } from "../types";
import { AptosConfig } from "./aptos_config";

/**
* A class to handle all `Coin` operations
*/
export class Coin {
readonly config: AptosConfig;

constructor(config: AptosConfig) {
this.config = config;
}

/**
* Generate a transfer coin transaction that can be simulated and/or signed and submitted
*
* @param args.sender The sender account
* @param args.recipient The recipient address
* @param args.amount The amount to transfer
* @param args.coinType optional. The coin struct type to transfer. Default to 0x1::aptos_coin::AptosCoin
*
* @returns SingleSignerTransaction
*/
async transferCoinTransaction(args: {
sender: Account;
recipient: HexInput;
amount: AnyNumber;
coinType?: MoveResourceType;
options?: GenerateTransactionOptions;
}): Promise<SingleSignerTransaction> {
const response = await transferCoinTransaction({ aptosConfig: this.config, ...args });
return response;
}
}
29 changes: 29 additions & 0 deletions src/api/transaction_submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,33 @@ export class TransactionSubmission {
const data = await submitTransaction({ aptosConfig: this.config, ...args });
return data;
}

/**
* Sign and submit a single signer transaction to chain
*
* @param args.signer The signer account to sign the transaction
* @param args.transaction A raw transaction type (note that it holds the raw transaction as a bcs serialized data)
* ```
* {
* rawTransaction: Uint8Array,
* secondarySignerAddresses? : Array<AccountAddress>,
* feePayerAddress?: AccountAddress
* }
* ```
*
* @return PendingTransactionResponse
*/
async signAndSubmitTransaction(args: {
signer: Account;
transaction: AnyRawTransaction;
}): Promise<PendingTransactionResponse> {
const { signer, transaction } = args;
const authenticator = signTransaction({ signer, transaction });
const response = await submitTransaction({
aptosConfig: this.config,
transaction,
senderAuthenticator: authenticator,
});
return response;
}
}
32 changes: 32 additions & 0 deletions src/internal/coin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AptosConfig } from "../api/aptos_config";
import { U64 } from "../bcs/serializable/move-primitives";
import { Account, AccountAddress } from "../core";
import { GenerateTransactionOptions, SingleSignerTransaction } from "../transactions/types";
import { StructTag, TypeTagStruct } from "../transactions/typeTag/typeTag";
import { HexInput, AnyNumber, MoveResourceType } from "../types";
import { APTOS_COIN } from "../utils/const";
import { generateTransaction } from "./transaction_submission";

export async function transferCoinTransaction(args: {
aptosConfig: AptosConfig;
sender: Account;
recipient: HexInput;
amount: AnyNumber;
coinType?: MoveResourceType;
options?: GenerateTransactionOptions;
}): Promise<SingleSignerTransaction> {
const { aptosConfig, sender, recipient, amount, coinType, options } = args;
const coinStructType = coinType ?? APTOS_COIN;
const transaction = await generateTransaction({
aptosConfig,
sender: sender.accountAddress.toString(),
data: {
function: "0x1::aptos_account::transfer_coins",
type_arguments: [new TypeTagStruct(StructTag.fromString(coinStructType))],
arguments: [AccountAddress.fromHexInput({ input: recipient }), new U64(amount)],
},
options,
});

return transaction as SingleSignerTransaction;
}
76 changes: 76 additions & 0 deletions tests/e2e/api/coin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { AptosConfig, Network, Aptos, Account, Deserializer } from "../../../src";
import { waitForTransaction } from "../../../src/internal/transaction";
import { RawTransaction, TransactionPayloadEntryFunction } from "../../../src/transactions/instances";
import { TypeTagStruct } from "../../../src/transactions/typeTag/typeTag";
import { SigningScheme } from "../../../src/types";

describe("coin", () => {
test("it generates a transfer coin transaction with AptosCoin coin type", async () => {
const config = new AptosConfig({ network: Network.DEVNET });
const aptos = new Aptos(config);
const sender = Account.generate({ scheme: SigningScheme.Ed25519 });
const recipient = Account.generate({ scheme: SigningScheme.Ed25519 });
await aptos.fundAccount({ accountAddress: sender.accountAddress.toString(), amount: 100000000 });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we make these numbers constants for the test

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we need a test framework. will work on it after we have full test cover to our code (not a P0 for alpha version)


const transaction = await aptos.transferCoinTransaction({
sender,
recipient: recipient.accountAddress.toString(),
amount: 10,
});

const txnDeserializer = new Deserializer(transaction.rawTransaction);
const rawTransaction = RawTransaction.deserialize(txnDeserializer);
Comment on lines +21 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is an interesting way of handling it, where you need to deserialize by passing the deserializer in. Might be interesting if we also just provide a way to deserialize the bytes directly (if you only have that).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe @xbtmatt has some thoughts about it

const typeArgs = (rawTransaction.payload as TransactionPayloadEntryFunction).entryFunction.type_args;
expect((typeArgs[0] as TypeTagStruct).value.address.toString()).toBe("0x1");
expect((typeArgs[0] as TypeTagStruct).value.module_name.identifier).toBe("aptos_coin");
expect((typeArgs[0] as TypeTagStruct).value.name.identifier).toBe("AptosCoin");
});

test("it generates a transfer coin transaction with a custom coin type", async () => {
const config = new AptosConfig({ network: Network.DEVNET });
const aptos = new Aptos(config);
const sender = Account.generate({ scheme: SigningScheme.Ed25519 });
const recipient = Account.generate({ scheme: SigningScheme.Ed25519 });
await aptos.fundAccount({ accountAddress: sender.accountAddress.toString(), amount: 100000000 });

const transaction = await aptos.transferCoinTransaction({
sender,
recipient: recipient.accountAddress.toString(),
amount: 10,
coinType: "0x1::my_coin::type",
});

const txnDeserializer = new Deserializer(transaction.rawTransaction);
const rawTransaction = RawTransaction.deserialize(txnDeserializer);
const typeArgs = (rawTransaction.payload as TransactionPayloadEntryFunction).entryFunction.type_args;
expect((typeArgs[0] as TypeTagStruct).value.address.toString()).toBe("0x1");
expect((typeArgs[0] as TypeTagStruct).value.module_name.identifier).toBe("my_coin");
expect((typeArgs[0] as TypeTagStruct).value.name.identifier).toBe("type");
});

test("it transfers APT coin aomunt from sender to recipient", async () => {
const config = new AptosConfig({ network: Network.DEVNET });
const aptos = new Aptos(config);
const sender = Account.generate({ scheme: SigningScheme.Ed25519 });
const recipient = Account.generate({ scheme: SigningScheme.Ed25519 });

await aptos.fundAccount({ accountAddress: sender.accountAddress.toString(), amount: 100000000 });
const senderCoinsBefore = await aptos.getAccountCoinsData({ accountAddress: sender.accountAddress.toString() });

const transaction = await aptos.transferCoinTransaction({
sender,
recipient: recipient.accountAddress.toString(),
amount: 10,
});
const response = await aptos.signAndSubmitTransaction({ signer: sender, transaction });

await waitForTransaction({ aptosConfig: config, txnHash: response.hash });

const recipientCoins = await aptos.getAccountCoinsData({ accountAddress: recipient.accountAddress.toString() });
const senderCoinsAfter = await aptos.getAccountCoinsData({ accountAddress: sender.accountAddress.toString() });
Comment on lines +69 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to consider unioning HexInput with AccountAddress or only taking in AccountAddress, since this seems to be a common pattern that is being repeated around.

The wallet adapter can be less stringent probably.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most users will get this data from some servers, that will return the address as a string (or in some cases as Uint8Array) - that was the thought behind accepting HexInput as an input and not force them initialize AccountAddress when they want to query for an address


expect(recipientCoins[0].amount).toBe(10);
expect(recipientCoins[0].asset_type).toBe("0x1::aptos_coin::AptosCoin");
expect(senderCoinsAfter[0].amount).toBeLessThan(senderCoinsBefore[0].amount);
});
});
Loading