@iyonger/aptos-web3-bip44.js
Version:
Web3 SDK For Aptos
1,489 lines (1,374 loc) • 45.9 kB
text/typescript
import { Buffer } from "buffer/";
import * as bip39 from "@scure/bip39";
import * as english from "@scure/bip39/wordlists/english";
import fetch from "cross-fetch";
import assert from "assert";
import { TxnBuilderTypes } from "./transaction_builder";
import { AptosAccount } from "./aptos_account";
import { TokenClient } from "./token_client";
import { AptosClient, OptionalTransactionArgs } from "./aptos_client";
import { FaucetClient } from "./faucet_client";
import { HexString, MaybeHexString } from "./hex_string";
import { RawTransaction } from "./aptos_types";
import cache from "./utils/cache";
import { WriteResource } from "./generated/index";
import { MAX_U64_BIG_INT } from "./bcs/consts";
import * as BCS from "./bcs";
import * as Gen from "./generated/index";
import { AnyNumber } from "./bcs";
const COIN_TYPE = 637;
const MAX_ACCOUNTS = 5;
const ADDRESS_GAP = 10;
const coinTransferFunction = "0x1::aptos_account::transfer";
export interface TxnRequestRaw {
sender: MaybeHexString;
payload: Gen.EntryFunctionPayload;
options?: Partial<Gen.SubmitTransactionRequest>;
}
export interface TokenId {
property_version: string;
token_data_id: {
creator: string;
collection: string;
name: string;
};
}
export interface AccountMetaData {
derivationPath: string;
address: string;
publicKey?: string;
}
export interface Wallet {
code: string; // mnemonic
accounts: AccountMetaData[];
}
export class WalletClient {
faucetClient: FaucetClient;
aptosClient: AptosClient;
tokenClient: TokenClient;
constructor(node_url, faucet_url) {
this.faucetClient = new FaucetClient(node_url, faucet_url);
this.aptosClient = new AptosClient(node_url);
this.tokenClient = new TokenClient(this.aptosClient);
}
async submitTransactionHelper(
account: AptosAccount,
payload: Gen.EntryFunctionPayload,
options = { max_gas_amount: "4000" }
) {
try {
const txnRequest = await this.aptosClient.generateTransaction(
account.address(),
payload,
options
);
const signedTxn = await this.aptosClient.signTransaction(
account,
txnRequest
);
const res = await this.aptosClient.submitTransaction(signedTxn);
await this.aptosClient.waitForTransaction(res.hash);
return await Promise.resolve(res.hash);
} catch (err) {
return await Promise.reject(err);
}
}
/**
* Each mnemonic phrase corresponds to a single wallet
* Wallet can contain multiple accounts
* An account corresponds to a key pair + address
*
* Get all the accounts of a user from their mnemonic phrase
*
* @param code The mnemonic phrase (12 word)
* @returns Wallet object containing all accounts of a user
*/
async importWallet(code: string): Promise<Wallet> {
let flag = false;
let address = "";
let publicKey = "";
let derivationPath = "";
let authKey = "";
const accountMetaData: AccountMetaData[] = [];
for (let i = 0; i < MAX_ACCOUNTS; i += 1) {
flag = false;
address = "";
publicKey = "";
derivationPath = "";
authKey = "";
for (let j = 0; j < ADDRESS_GAP; j += 1) {
/* eslint-disable no-await-in-loop */
derivationPath = `m/44'/${COIN_TYPE}'/${i}'/0'/${j}'`;
const account = AptosAccount.fromDerivePath(derivationPath, code);
if (j === 0) {
address = HexString.ensure(account.address()).toShortString();
publicKey = account.pubKey().toString();
const response = await fetch(
`${this.aptosClient.nodeUrl}/accounts/${address}`,
{
method: "GET",
}
);
if (response.status === 404) {
// if the very first account is not present in the aptos, it will add this to metadata
if (i === 0) {
flag = true;
// create new account if it is not present
await this.createNewAccount(code);
}
break;
}
const respBody = await response.json();
authKey = respBody.authentication_key;
}
if (
account.authKey().toShortString() === authKey ||
account.authKey().toString() === authKey
) {
flag = true;
break;
}
/* eslint-enable no-await-in-loop */
}
if (!flag) {
break;
}
accountMetaData.push({
derivationPath,
address,
publicKey,
});
}
return { code, accounts: accountMetaData };
}
/**
* Creates a new wallet which contains a single account,
* which is registered on Aptos
*
* @returns A wallet object
*/
async createWallet(): Promise<Wallet> {
const code = bip39.generateMnemonic(english.wordlist); // mnemonic
const accountMetadata = await this.createNewAccount(code);
return { code, accounts: [accountMetadata] };
}
/**
* Creates a new account in the provided wallet
*
* @param code mnemonic phrase of the wallet
* @param index index for the derivation path
* @returns
*/
// eslint-disable-next-line class-methods-use-this
createNewAccount(code: string, index: number = 0): AccountMetaData {
if (index > MAX_ACCOUNTS) {
throw new Error("Max no. of accounts reached");
}
const derivationPath = `m/44'/${COIN_TYPE}'/${index}'/0'/0'`;
const account = AptosAccount.fromDerivePath(derivationPath, code);
const address = HexString.ensure(account.address()).toShortString();
return {
derivationPath,
address,
publicKey: account.pubKey().toString(),
};
}
/** Generates a transaction request that can be submitted to produce a raw transaction that
* can be signed, which upon being signed can be submitted to the blockchain
* @param sender Hex-encoded 32 byte Aptos account address of transaction sender
* @param payload Transaction payload. It depends on transaction type you want to send
* @param options Options allow to overwrite default transaction options.
* Defaults are:
* ```bash
* {
* sender: senderAddress.hex(),
* sequence_number: account.sequence_number,
* max_gas_amount: "1000",
* gas_unit_price: "1",
* // Unix timestamp, in seconds + 10 seconds
* expiration_timestamp_secs: (Math.floor(Date.now() / 1000) + 10).toString(),
* }
* ```
* @returns Serialized form of RawTransaction: Uint8Array
*/
async generateTransactionSerialized(
sender: MaybeHexString,
payload: Gen.EntryFunctionPayload,
options?: Partial<Gen.SubmitTransactionRequest>
): Promise<Uint8Array> {
const txnReq = await this.aptosClient.generateTransaction(
sender,
payload,
options
);
const serializer = new BCS.Serializer();
txnReq.serialize(serializer);
return serializer.getBytes();
}
/**
* returns an RawTransaction object from serialized bytes
*
* @param bytes Buffer
* @returns RawTransaction Object
*/
static getTransactionDeserialized(
bytes: Uint8Array
): TxnBuilderTypes.RawTransaction {
const deserializer = new BCS.Deserializer(bytes);
return RawTransaction.deserialize(deserializer);
}
/**
* returns an AptosAccount object given a private key and
* address of the account
*
* @param privateKey Private key of an account as a Buffer
* @param address address of a user
* @returns AptosAccount object
*/
static getAccountFromPrivateKey(privateKey: Buffer, address?: string) {
return new AptosAccount(privateKey, address);
}
/**
* returns an AptosAccount at position m/44'/COIN_TYPE'/0'/0/0
*
* @param code mnemonic phrase of the wallet
* @returns AptosAccount object
*/
static getAccountFromMnemonic(code: string) {
return AptosAccount.fromDerivePath(`m/44'/${COIN_TYPE}'/0'/0'/0'`, code);
}
/**
* returns an AptosAccount object for the desired account
* using the metadata of the account
*
* @param code mnemonic phrase of the wallet
* @param metaData metadata of the account to be fetched
* @returns
*/
static getAccountFromMetaData(code: string, metaData: AccountMetaData) {
return AptosAccount.fromDerivePath(
metaData.derivationPath,
code,
metaData.address
);
}
/**
* airdrops test coins in the given account
*
* @param address address of the receiver's account
* @param amount amount to be airdropped
* @returns list of transaction hashs
*/
async airdrop(address: string, amount: number) {
return Promise.resolve(
await this.faucetClient.fundAccount(address, amount)
);
}
/**
* returns the balance of the said account
*
* @param address address of the desired account
* @returns balance of the account
*/
async getBalance(address: string | HexString) {
let balance = 0;
const resources: any = await this.aptosClient.getAccountResources(address);
Object.values(resources).forEach((value: any) => {
if (value.type === "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>") {
balance = Number(value.data.coin.value);
}
});
return Promise.resolve(balance);
}
/**
* returns the list of on-chain transactions sent by the said account
*
* @param accountAddress address of the desired account
* @returns list of transactions
*/
async accountTransactions(accountAddress: MaybeHexString) {
const data = await this.aptosClient.getAccountTransactions(accountAddress);
const transactions = data.map((item: any) => ({
data: item.payload,
from: item.sender,
gas: item.gas_used,
gasPrice: item.gas_unit_price,
hash: item.hash,
success: item.success,
timestamp: item.timestamp,
toAddress: item.payload.arguments[0],
price: item.payload.arguments[1],
type: item.type,
version: item.version,
vmStatus: item.vm_status,
}));
return transactions;
}
/**
* transfers Aptos Coins from signer to receiver
*
* @param account AptosAccount object of the signing account
* @param recipient_address address of the receiver account
* @param amount amount of aptos coins to be transferred
* @returns transaction hash
*/
async transfer(
account: AptosAccount,
recipient_address: string | HexString,
amount: number
) {
try {
if (recipient_address.toString() === account.address().toString()) {
return new Error("cannot transfer coins to self");
}
const payload: Gen.EntryFunctionPayload = {
function: coinTransferFunction,
type_arguments: [],
arguments: [recipient_address, amount],
};
const rawTxn: TxnBuilderTypes.RawTransaction =
await this.aptosClient.generateTransaction(account.address(), payload);
const signedTxn: Uint8Array = await this.aptosClient.signTransaction(
account,
rawTxn
);
const transaction: Gen.PendingTransaction =
await this.aptosClient.submitTransaction(signedTxn);
await this.aptosClient.waitForTransaction(transaction.hash);
return await Promise.resolve(transaction.hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* returns the list of events involving transactions
* starting from the said account
*
* @param address address of the desired account
* @returns list of events
*/
async getSentEvents(
address: MaybeHexString,
limit?: number,
start?: AnyNumber
) {
return Promise.resolve(
await this.aptosClient.getAccountTransactions(address, { start, limit })
);
}
/**
* returns the list of events involving transactions of Aptos Coins
* received by the said account
*
* @param address address of the desired account
* @returns list of events
*/
async getReceivedEvents(address: string, limit?: number, start?: AnyNumber) {
const eventHandleStruct =
"0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>";
return Promise.resolve(
await this.aptosClient.getEventsByEventHandle(
address,
eventHandleStruct,
"deposit_events",
{ start, limit }
)
);
}
/**
* creates an NFT collection
*
* @param account AptosAccount object of the signing account
* @param name collection name
* @param description collection description
* @param uri collection URI
* @returns transaction hash
*/
async createCollection(
account: AptosAccount,
name: string,
description: string,
uri: string,
maxAmount: AnyNumber = MAX_U64_BIG_INT,
extraArgs?: OptionalTransactionArgs
) {
return Promise.resolve(
await this.tokenClient.createCollection(
account,
name,
description,
uri,
maxAmount,
extraArgs
)
);
}
/**
* creates an NFT
*
* @param account AptosAccount object of the signing account
* @param collection_name collection name
* @param name NFT name
* @param description NFT description
* @param supply supply for the NFT
* @param uri NFT URI
* @param royalty_points_per_million royalty points per million
* @returns transaction hash
*/
async createToken(
account: AptosAccount,
collection_name: string,
name: string,
description: string,
supply: number,
uri: string,
max: BCS.AnyNumber = MAX_U64_BIG_INT,
royalty_payee_address: MaybeHexString = account.address(),
royalty_points_denominator: number = 0,
royalty_points_numerator: number = 0,
property_keys: Array<string> = [],
property_values: Array<string> = [],
property_types: Array<string> = [],
extraArgs?: OptionalTransactionArgs
) {
return Promise.resolve(
await this.tokenClient.createToken(
account,
collection_name,
name,
description,
supply,
uri,
max,
royalty_payee_address,
royalty_points_denominator,
royalty_points_numerator,
property_keys,
property_values,
property_types,
extraArgs
)
);
}
/**
* offers an NFT to another account
*
* @param account AptosAccount object of the signing account
* @param receiver_address address of the receiver account
* @param creator_address address of the creator account
* @param collection_name collection name
* @param token_name NFT name
* @param amount amount to receive while offering the token
* @returns transaction hash
*/
async offerToken(
account: AptosAccount,
receiver_address: string,
creator_address: string,
collection_name: string,
token_name: string,
amount: number,
property_version: number = 0,
extraArgs?: OptionalTransactionArgs
) {
return Promise.resolve(
await this.tokenClient.offerToken(
account,
receiver_address,
creator_address,
collection_name,
token_name,
amount,
property_version,
extraArgs
)
);
}
/**
* cancels an NFT offer
*
* @param account AptosAccount of the signing account
* @param receiver_address address of the receiver account
* @param creator_address address of the creator account
* @param collection_name collection name
* @param token_name NFT name
* @returns transaction hash
*/
async cancelTokenOffer(
account: AptosAccount,
receiver_address: string,
creator_address: string,
collection_name: string,
token_name: string,
property_version: number = 0,
extraArgs?: OptionalTransactionArgs
) {
return Promise.resolve(
await this.tokenClient.cancelTokenOffer(
account,
receiver_address,
creator_address,
collection_name,
token_name,
property_version,
extraArgs
)
);
}
/**
* claims offered NFT
*
* @param account AptosAccount of the signing account
* @param sender_address address of the sender account
* @param creator_address address of the creator account
* @param collection_name collection name
* @param token_name NFT name
* @returns transaction hash
*/
async claimToken(
account: AptosAccount,
sender_address: string,
creator_address: string,
collection_name: string,
token_name: string,
property_version: number = 0,
extraArgs?: OptionalTransactionArgs
) {
return Promise.resolve(
await this.tokenClient.claimToken(
account,
sender_address,
creator_address,
collection_name,
token_name,
property_version,
extraArgs
)
);
}
/**
* Opt in to receive nft transfers from other accounts
*
* @param account AptosAccount which has to opt in for receiving nft transfers
* @param opt_in Boolean value of whether to opt in or not
* @returns The hash of the transaction submitted to the API
*/
async optInDirectTransfer(account: AptosAccount, opt_in: Boolean) {
try {
const payload: Gen.EntryFunctionPayload = {
function: "0x3::token::opt_in_direct_transfer",
type_arguments: [],
arguments: [opt_in],
};
const rawTxn: TxnBuilderTypes.RawTransaction =
await this.aptosClient.generateTransaction(account.address(), payload);
const signedTxn: Uint8Array = await this.aptosClient.signTransaction(
account,
rawTxn
);
const transaction: Gen.PendingTransaction =
await this.aptosClient.submitTransaction(signedTxn);
await this.aptosClient.waitForTransaction(transaction.hash);
return await Promise.resolve(transaction.hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Transfer the specified amount of tokens from account to receiver
* Receiver must have opted in for direct transfers
*
* @param sender AptosAccount where token from which tokens will be transfered
* @param receiver Hex-encoded 32 byte Aptos account address to which tokens will be transfered
* @param creator Hex-encoded 32 byte Aptos account address to which created tokens
* @param collectionName Name of collection where token is stored
* @param name Token name
* @param amount Amount of tokens which will be transfered
* @param propertyVersion the version of token PropertyMap with a default value 0.
* @returns The hash of the transaction submitted to the API
*/
async transferWithOptIn(
sender: AptosAccount,
receiver: MaybeHexString,
creator: MaybeHexString,
collectionName: string,
name: string,
amount: number,
propertyVersion: number = 0
) {
try {
const payload: Gen.EntryFunctionPayload = {
function: "0x3::token::transfer_with_opt_in",
type_arguments: [],
arguments: [
creator,
collectionName,
name,
propertyVersion,
receiver,
amount,
],
};
const rawTxn: TxnBuilderTypes.RawTransaction =
await this.aptosClient.generateTransaction(sender.address(), payload);
const signedTxn: Uint8Array = await this.aptosClient.signTransaction(
sender,
rawTxn
);
const transaction: Gen.PendingTransaction =
await this.aptosClient.submitTransaction(signedTxn);
await this.aptosClient.waitForTransaction(transaction.hash);
return await Promise.resolve(transaction.hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* sign a generic transaction
*
* @param account AptosAccount of the signing account
* @param func function name to be called
* @param args arguments of the function to be called
* @param type_args type arguments of the function to be called
* @returns transaction hash
*/
async signGenericTransaction(
account: AptosAccount,
func: string,
args: string[],
type_args: string[]
) {
const payload: Gen.TransactionPayload = {
type: "entry_function_payload",
function: func,
type_arguments: type_args,
arguments: args,
};
const txnHash = await this.submitTransactionHelper(account, payload);
const resp: any = await this.aptosClient.getTransactionByHash(txnHash);
const status = { success: resp.success, vm_status: resp.vm_status };
return { txnHash, ...status };
}
async signAndSubmitTransaction(
account: AptosAccount,
txnRequest: TxnBuilderTypes.RawTransaction
) {
const signedTxn = await this.aptosClient.signTransaction(
account,
txnRequest
);
const res = await this.aptosClient.submitTransaction(signedTxn);
await this.aptosClient.waitForTransaction(res.hash);
return Promise.resolve(res.hash);
}
// sign and submit multiple transactions
async signAndSubmitTransactions(
account: AptosAccount,
txnRequests: TxnRequestRaw[]
) {
const hashs = [];
// eslint-disable-next-line no-restricted-syntax
for (const rawTxn of txnRequests) {
/* eslint-disable no-await-in-loop */
try {
const txnRequest = await this.aptosClient.generateTransaction(
rawTxn.sender,
rawTxn.payload,
rawTxn.options
);
const signedTxn = await this.aptosClient.signTransaction(
account,
txnRequest
);
const res = await this.aptosClient.submitTransaction(signedTxn);
await this.aptosClient.waitForTransaction(res.hash);
hashs.push(res.hash);
} catch (err) {
hashs.push(err.message);
}
/* eslint-enable no-await-in-loop */
}
return Promise.resolve(hashs);
}
async signTransaction(
account: AptosAccount,
txnRequest: TxnBuilderTypes.RawTransaction
): Promise<Uint8Array> {
return Promise.resolve(
await this.aptosClient.signTransaction(account, txnRequest)
);
}
async estimateGasFees(
accountPublicKey: MaybeHexString,
transaction: TxnBuilderTypes.RawTransaction
): Promise<string> {
const simulateResponse: any = await this.aptosClient.simulateTransaction(
HexString.ensure(accountPublicKey),
transaction
);
return (
parseInt(simulateResponse[0].gas_used, 10) *
parseInt(simulateResponse[0].gas_unit_price, 10)
).toString();
}
async estimateCost(
accountAddress: MaybeHexString,
accountPublicKey: MaybeHexString,
transaction: TxnBuilderTypes.RawTransaction
): Promise<string> {
const simulateResponse: any = await this.aptosClient.simulateTransaction(
HexString.ensure(accountPublicKey),
transaction
);
const txnData = simulateResponse[0];
const currentBalance = await this.getBalance(accountAddress);
const change = txnData.changes.filter((ch) => {
if (ch.type !== "write_resource") {
return false;
}
const write = ch as WriteResource;
if (
write.data.type ===
"0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>" &&
write.address === accountAddress.toString()
) {
return true;
}
return false;
});
if (change.length > 0) {
/* eslint-disable @typescript-eslint/dot-notation */
return (
currentBalance -
parseInt(change[0]["data"].data["coin"].value, 10) -
parseInt(txnData.gas_used, 10) * parseInt(txnData.gas_unit_price, 10)
).toString();
}
return "0";
}
async submitTransaction(signedTxn: Uint8Array) {
return Promise.resolve(await this.aptosClient.submitTransaction(signedTxn));
}
static generateBCSTransaction(
account: AptosAccount,
rawTxn: RawTransaction
): Promise<Uint8Array> {
return Promise.resolve(AptosClient.generateBCSTransaction(account, rawTxn));
}
static generateBCSSimulation(
account: AptosAccount,
rawTxn: RawTransaction
): Promise<Uint8Array> {
return Promise.resolve(
AptosClient.generateBCSSimulation(account.pubKey(), rawTxn)
);
}
async submitSignedBCSTransaction(
signedTxn: Uint8Array
): Promise<Gen.PendingTransaction> {
return Promise.resolve(
await this.aptosClient.submitSignedBCSTransaction(signedTxn)
);
}
async submitBCSSimulation(
bcsBody: Uint8Array
): Promise<Gen.UserTransaction[]> {
return Promise.resolve(await this.aptosClient.submitBCSSimulation(bcsBody));
}
static signMessage(account: AptosAccount, message: string): Promise<string> {
return Promise.resolve(account.signBuffer(Buffer.from(message)).hex());
}
/**
* Rotates the auth key
* Disabled
*
* @param code mnemonic phrase for the desired wallet
* @param metaData metadata for the desired account
* @returns status object
*/
/* eslint-disable */
async rotateAuthKey(code: string, metaData: AccountMetaData) {
// const account: AptosAccount = await WalletClient.getAccountFromMetaData(
// code,
// metaData
// );
// const pathSplit = metaData.derivationPath.split("/");
// const addressIndex = Number(pathSplit[pathSplit.length - 1].slice(0, -1));
// if (addressIndex >= ADDRESS_GAP - 1) {
// throw new Error("Maximum key rotation reached");
// }
// const newDerivationPath = `${pathSplit
// .slice(0, pathSplit.length - 1)
// .join("/")}/${addressIndex + 1}'`;
// const newAccount = await WalletClient.getAccountFromMetaData(code, {
// address: metaData.address,
// derivationPath: newDerivationPath,
// });
// const newAuthKey = newAccount.authKey().noPrefix();
// const transactionStatus = await this.signGenericTransaction(
// account,
// "0x1::account::rotate_authentication_key_25519",
// [account.pubKey().toString(), account.],
// []
// );
// if (!transactionStatus.success) {
// return {
// authkey: "",
// success: false,
// vm_status: transactionStatus.vm_status,
// };
// }
return {
authkey: "0x",
success: false,
vm_status: "disabled",
};
}
/* eslint-enable */
async getEventStream(
address: string,
eventHandleStruct: string,
fieldName: string,
limit?: number,
start?: number
) {
let endpointUrl = `${this.aptosClient.nodeUrl}/accounts/${address}/events/${eventHandleStruct}/${fieldName}`;
if (limit) {
endpointUrl += `?limit=${limit}`;
}
if (start) {
endpointUrl += limit ? `&start=${start}` : `?start=${start}`;
}
const response = await fetch(endpointUrl, {
method: "GET",
});
if (response.status === 404) {
return [];
}
return Promise.resolve(await response.json());
}
/**
* returns a list of token IDs of the tokens in a user's account
* (including the tokens that were minted)
*
* @param address address of the desired account
* @returns list of token IDs
*/
async getTokenIds(
address: string,
limit?: number,
depositStart?: number,
withdrawStart?: number
) {
const countDeposit = {};
const countWithdraw = {};
const elementsFetched = new Set();
const tokenIds = [];
const depositEvents = await this.getEventStream(
address,
"0x3::token::TokenStore",
"deposit_events",
limit,
depositStart
);
const withdrawEvents = await this.getEventStream(
address,
"0x3::token::TokenStore",
"withdraw_events",
limit,
withdrawStart
);
let maxDepositSequenceNumber = -1;
let maxWithdrawSequenceNumber = -1;
depositEvents.forEach((element) => {
const elementString = JSON.stringify(element.data.id);
elementsFetched.add(elementString);
countDeposit[elementString] = countDeposit[elementString]
? {
count: countDeposit[elementString].count + 1,
sequence_number: element.sequence_number,
data: element.data.id,
}
: {
count: 1,
sequence_number: element.sequence_number,
data: element.data.id,
};
maxDepositSequenceNumber = Math.max(
maxDepositSequenceNumber,
parseInt(element.sequence_number, 10)
);
});
withdrawEvents.forEach((element) => {
const elementString = JSON.stringify(element.data.id);
elementsFetched.add(elementString);
countWithdraw[elementString] = countWithdraw[elementString]
? {
count: countWithdraw[elementString].count + 1,
sequence_number: element.sequence_number,
data: element.data.id,
}
: {
count: 1,
sequence_number: element.sequence_number,
data: element.data.id,
};
maxWithdrawSequenceNumber = Math.max(
maxWithdrawSequenceNumber,
parseInt(element.sequence_number, 10)
);
});
if (elementsFetched) {
Array.from(elementsFetched).forEach((elementString: string) => {
const depositEventCount = countDeposit[elementString]
? countDeposit[elementString].count
: 0;
const withdrawEventCount = countWithdraw[elementString]
? countWithdraw[elementString].count
: 0;
tokenIds.push({
data: countDeposit[elementString]
? countDeposit[elementString].data
: countWithdraw[elementString].data,
deposit_sequence_number: countDeposit[elementString]
? countDeposit[elementString].sequence_number
: "-1",
withdraw_sequence_number: countWithdraw[elementString]
? countWithdraw[elementString].sequence_number
: "-1",
difference: depositEventCount - withdrawEventCount,
});
});
}
return { tokenIds, maxDepositSequenceNumber, maxWithdrawSequenceNumber };
}
/**
* returns the tokens in an account
*
* @param address address of the desired account
* @returns list of tokens and their collection data
*/
async getTokens(
address: string,
limit?: number,
depositStart?: number,
withdrawStart?: number
) {
const { tokenIds } = await this.getTokenIds(
address,
limit,
depositStart,
withdrawStart
);
const tokens = [];
await Promise.all(
tokenIds.map(async (tokenId) => {
try {
let resources: Gen.MoveResource[];
if (cache.has(`resources--${tokenId.data.token_data_id.creator}`)) {
resources = cache.get(
`resources--${tokenId.data.token_data_id.creator}`
);
} else {
resources = await this.aptosClient.getAccountResources(
tokenId.data.token_data_id.creator
);
cache.set(
`resources--${tokenId.data.token_data_id.creator}`,
resources
);
}
const accountResource: { type: string; data: any } = resources.find(
(r) => r.type === "0x3::token::Collections"
);
const tableItemRequest: Gen.TableItemRequest = {
key_type: "0x3::token::TokenDataId",
value_type: "0x3::token::TokenData",
key: tokenId.data.token_data_id,
};
const cacheKey = JSON.stringify(tableItemRequest);
let token: any;
if (cache.has(cacheKey)) {
token = cache.get(cacheKey);
} else {
token = await this.aptosClient.getTableItem(
accountResource.data.token_data.handle,
tableItemRequest
);
cache.set(cacheKey, token);
}
token.collection = tokenId.data.token_data_id.collection;
tokens.push({ token, sequence_number: tokenId.sequence_number });
} catch (e) {
// Errors happening because of token handle not found will lead here
}
})
);
return tokens;
}
/**
* returns the token information (including the collection information)
* about a said tokenID
*
* @param tokenId token ID of the desired token
* @returns token information
*/
async getToken(tokenId: TokenId, resourceHandle?: string) {
let accountResource: { type: string; data: any };
if (!resourceHandle) {
const resources: Gen.MoveResource[] =
await this.aptosClient.getAccountResources(
tokenId.token_data_id.creator
);
accountResource = resources.find(
(r) => r.type === "0x3::token::Collections"
);
}
const tableItemRequest: Gen.TableItemRequest = {
key_type: "0x3::token::TokenDataId",
value_type: "0x3::token::TokenData",
key: tokenId.token_data_id,
};
const token = await this.aptosClient.getTableItem(
resourceHandle || accountResource.data.token_data.handle,
tableItemRequest
);
token.collection = tokenId.token_data_id.collection;
return token;
}
/**
* returns the resource handle for type 0x3::token::Collections
* about a said creator
*
* @param tokenId token ID of the desired token
* @returns resource information
*/
async getTokenResourceHandle(tokenId: TokenId) {
const resources: Gen.MoveResource[] =
await this.aptosClient.getAccountResources(tokenId.token_data_id.creator);
const accountResource: { type: string; data: any } = resources.find(
(r) => r.type === "0x3::token::Collections"
);
return accountResource.data.token_data.handle;
}
/**
* returns the information about a collection of an account
*
* @param address address of the desired account
* @param collectionName collection name
* @returns collection information
*/
async getCollection(address: string, collectionName: string) {
const resources: Gen.MoveResource[] =
await this.aptosClient.getAccountResources(address);
const accountResource: { type: string; data: any } = resources.find(
(r) => r.type === "0x3::token::Collections"
);
const tableItemRequest: Gen.TableItemRequest = {
key_type: "0x1::string::String",
value_type: "0x3::token::Collection",
key: collectionName,
};
const collection = await this.aptosClient.getTableItem(
accountResource.data.collections.handle,
tableItemRequest
);
return collection;
}
async getCustomResource(
address: string,
resourceType: string,
fieldName: string,
keyType: string,
valueType: string,
key: any
) {
const resources: any = await this.aptosClient.getAccountResources(address);
const accountResource: { type: string; data: any } = resources.find(
(r) => r.type === resourceType
);
const tableItemRequest: Gen.TableItemRequest = {
key_type: keyType,
value_type: valueType,
key,
};
const resource = await this.aptosClient.getTableItem(
accountResource.data[fieldName].handle,
tableItemRequest
);
return resource;
}
/**
* returns info about a particular resource inside an account
*
* @param accountAddress address of the desired account
* @param resourceType type of the desired resource
* @returns resource information
*/
async getAccountResource(
accountAddress: string,
resourceType: string
): Promise<any> {
const response = await fetch(
`${this.aptosClient.nodeUrl}/accounts/${accountAddress}/resource/${resourceType}`,
{ method: "GET" }
);
if (response.status === 404) {
return null;
}
if (response.status !== 200) {
assert(response.status === 200, await response.text());
}
return Promise.resolve(await response.json());
}
/**
* initializes a coin
*
* precondition: a module of the desired coin has to be deployed in the signer's account
*
* @param account AptosAccount object of the signing account
* @param coin_type_path address path of the desired coin
* @param name name of the coin
* @param symbol symbol of the coin
* @param scaling_factor scaling factor of the coin
* @returns transaction hash
*/
async initializeCoin(
account: AptosAccount,
coin_type_path: string, // coin_type_path: something like 0x${coinTypeAddress}::moon_coin::MoonCoin
name: string,
symbol: string,
scaling_factor: number
) {
const payload: Gen.TransactionPayload = {
type: "entry_function_payload",
function: "0x1::managed_coin::initialize",
type_arguments: [coin_type_path],
arguments: [name, symbol, scaling_factor, false],
};
const txnHash = await this.submitTransactionHelper(account, payload);
const resp: any = await this.aptosClient.getTransactionByHash(txnHash);
const status = { success: resp.success, vm_status: resp.vm_status };
return { txnHash, ...status };
}
/**
* registers a coin for an account
*
* creates the resource for the desired account such that
* the account can start transacting in the desired coin
*
* @param account AptosAccount object of the signing account
* @param coin_type_path address path of the desired coin
* @returns transaction hash
*/
async registerCoin(account: AptosAccount, coin_type_path: string) {
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString(coin_type_path)
);
const entryFunctionPayload =
new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::managed_coin",
"register",
[token],
[]
)
);
const rawTxn = await this.aptosClient.generateRawTransaction(
account.address(),
entryFunctionPayload
);
const bcsTxn = AptosClient.generateBCSTransaction(account, rawTxn);
const transactionRes = await this.aptosClient.submitSignedBCSTransaction(
bcsTxn
);
await this.aptosClient.waitForTransaction(transactionRes.hash);
const resp: any = await this.aptosClient.getTransactionByHash(
transactionRes.hash
);
const status = { success: resp.success, vm_status: resp.vm_status };
const txnHash = transactionRes.hash;
return { txnHash, ...status };
}
/**
* mints a coin in a receiver account
*
* precondition: the signer should have minting capability
* unless specifically granted, only the account where the module
* of the desired coin lies has the minting capability
*
* @param account AptosAccount object of the signing account
* @param coin_type_path address path of the desired coin
* @param dst_address address of the receiver account
* @param amount amount to be minted
* @returns transaction hash
*/
async mintCoin(
account: AptosAccount,
coin_type_path: string, // coin_type_path: something like 0x${coinTypeAddress}::moon_coin::MoonCoin
dst_address: string,
amount: number
) {
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString(coin_type_path)
);
const entryFunctionPayload =
new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::managed_coin",
"mint",
[token],
[
BCS.bcsToBytes(
TxnBuilderTypes.AccountAddress.fromHex(
HexString.ensure(dst_address).toString()
)
),
BCS.bcsSerializeUint64(amount),
]
)
);
const rawTxn = await this.aptosClient.generateRawTransaction(
account.address(),
entryFunctionPayload
);
const bcsTxn = AptosClient.generateBCSTransaction(account, rawTxn);
const transactionRes = await this.aptosClient.submitSignedBCSTransaction(
bcsTxn
);
await this.aptosClient.waitForTransaction(transactionRes.hash);
const resp: any = await this.aptosClient.getTransactionByHash(
transactionRes.hash
);
const status = { success: resp.success, vm_status: resp.vm_status };
const txnHash = transactionRes.hash;
return { txnHash, ...status };
}
/**
* transfers coin (applicable for all altcoins on Aptos) to receiver account
*
* @param account AptosAccount object of the signing account
* @param coin_type_path address path of the desired coin
* @param to_address address of the receiver account
* @param amount amount to be transferred
* @returns transaction hash
*/
async transferCoin(
account: AptosAccount,
coin_type_path: string, // coin_type_path: something like 0x${coinTypeAddress}::moon_coin::MoonCoin
to_address: string,
amount: number
) {
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString(coin_type_path)
);
const entryFunctionPayload =
new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::coin",
"transfer",
[token],
[
BCS.bcsToBytes(
TxnBuilderTypes.AccountAddress.fromHex(
HexString.ensure(to_address).toString()
)
),
BCS.bcsSerializeUint64(amount),
]
)
);
const rawTxn = await this.aptosClient.generateRawTransaction(
account.address(),
entryFunctionPayload
);
const bcsTxn = AptosClient.generateBCSTransaction(account, rawTxn);
const transactionRes = await this.aptosClient.submitSignedBCSTransaction(
bcsTxn
);
await this.aptosClient.waitForTransaction(transactionRes.hash);
const resp: any = await this.aptosClient.getTransactionByHash(
transactionRes.hash
);
const status = { success: resp.success, vm_status: resp.vm_status };
const txnHash = transactionRes.hash;
return { txnHash, ...status };
}
/**
* returns the information about the coin
*
* @param coin_type_path address path of the desired coin
* @returns coin information
*/
async getCoinData(coin_type_path: string) {
const coinData = await this.getAccountResource(
coin_type_path.split("::")[0],
`0x1::coin::CoinInfo<${coin_type_path}>`
);
return coinData;
}
/**
* returns the balance of the coin for an account
*
* @param address address of the desired account
* @param coin_type_path address path of the desired coin
* @returns number of coins
*/
async getCoinBalance(
address: string,
coin_type_path: string
): Promise<number> {
// coin_type_path: something like 0x${coinTypeAddress}::moon_coin::MoonCoin
const coinInfo = await this.getAccountResource(
address,
`0x1::coin::CoinStore<${coin_type_path}>`
);
return Number(coinInfo.data.coin.value);
}
/**
* returns the list of all the custom coins for an account
*
* @param address address of the desired account
* @returns array of coins with their data
*/
async getCustomCoins(address: string) {
const coins = [];
const resources: any = await this.aptosClient.getAccountResources(address);
await Promise.all(
Object.values(resources).map(async (value: any) => {
if (
value.type.startsWith("0x1::coin::CoinStore") &&
value.type !== "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
) {
const coinTypePath: string = value.type.substring(
value.type.indexOf("<") + 1,
value.type.lastIndexOf(">")
);
const coinData = await this.getCoinData(coinTypePath);
coins.push({
balance: Number(value.data.coin.value),
name: coinData.data.symbol,
decimals: coinData.data.decimals,
coinName: coinData.data.name,
coinAddress: coinTypePath,
});
}
})
);
return coins;
}
async publishModule(
sender: AptosAccount,
packageMetadataHex: string,
moduleHex: string,
extraArgs?: OptionalTransactionArgs
) {
const txnHash = await this.aptosClient.publishPackage(
sender,
new HexString(packageMetadataHex).toUint8Array(),
[new TxnBuilderTypes.Module(new HexString(moduleHex).toUint8Array())],
extraArgs
);
return txnHash;
}
}