@iyonger/aptos-web3-bip44.js
Version:
Web3 SDK For Aptos
613 lines (528 loc) • 23.6 kB
text/typescript
// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0
import { AptosClient } from "./aptos_client";
import * as Gen from "./generated/index";
import { FaucetClient } from "./faucet_client";
import { AptosAccount } from "./aptos_account";
import {
TxnBuilderTypes,
TransactionBuilderMultiEd25519,
TransactionBuilderRemoteABI,
} from "./transaction_builder";
import { TokenClient } from "./token_client";
import { HexString } from "./hex_string";
import { FAUCET_URL, NODE_URL } from "./utils/test_helper.test";
import { bcsSerializeUint64, bcsToBytes } from "./bcs";
const account = "0x1::account::Account";
const aptosCoin = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>";
const coinTransferFunction = "0x1::coin::transfer";
test("node url empty", () => {
expect(() => {
const client = new AptosClient("");
client.getAccount("0x1");
}).toThrow("Node URL cannot be empty.");
});
test("gets genesis account", async () => {
const client = new AptosClient(NODE_URL);
const genesisAccount = await client.getAccount("0x1");
expect(genesisAccount.authentication_key.length).toBe(66);
expect(genesisAccount.sequence_number).not.toBeNull();
});
test("gets transactions", async () => {
const client = new AptosClient(NODE_URL);
const transactions = await client.getTransactions();
expect(transactions.length).toBeGreaterThan(0);
});
test("gets genesis resources", async () => {
const client = new AptosClient(NODE_URL);
const resources = await client.getAccountResources("0x1");
const accountResource = resources.find((r) => r.type === account);
expect(accountResource).toBeDefined();
});
test("gets the Account resource", async () => {
const client = new AptosClient(NODE_URL);
const accountResource = await client.getAccountResource("0x1", account);
expect(accountResource).toBeDefined();
});
test("gets ledger info", async () => {
const client = new AptosClient(NODE_URL);
const ledgerInfo = await client.getLedgerInfo();
expect(ledgerInfo.chain_id).toBeGreaterThan(1);
expect(parseInt(ledgerInfo.ledger_version, 10)).toBeGreaterThan(0);
});
test("gets account modules", async () => {
const client = new AptosClient(NODE_URL);
const modules = await client.getAccountModules("0x1");
const module = modules.find((r) => r.abi!.name === "aptos_coin");
expect(module!.abi!.address).toBe("0x1");
});
test("gets the AptosCoin module", async () => {
const client = new AptosClient(NODE_URL);
const module = await client.getAccountModule("0x1", "aptos_coin");
expect(module!.abi!.address).toBe("0x1");
});
test(
"submits bcs transaction",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const account1 = new AptosAccount();
await faucetClient.fundAccount(account1.address(), 100_000_000);
let resources = await client.getAccountResources(account1.address());
let accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("100000000");
const account2 = new AptosAccount();
await faucetClient.fundAccount(account2.address(), 0);
resources = await client.getAccountResources(account2.address());
accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("0");
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString("0x1::aptos_coin::AptosCoin")
);
const entryFunctionPayload =
new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::coin",
"transfer",
[token],
[
bcsToBytes(
TxnBuilderTypes.AccountAddress.fromHex(account2.address())
),
bcsSerializeUint64(717),
]
)
);
const rawTxn = await client.generateRawTransaction(
account1.address(),
entryFunctionPayload
);
const bcsTxn = AptosClient.generateBCSTransaction(account1, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
await client.waitForTransaction(transactionRes.hash);
resources = await client.getAccountResources(account2.address());
accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("717");
},
30 * 1000
);
test(
"submits transaction with remote ABI",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const account1 = new AptosAccount();
await faucetClient.fundAccount(account1.address(), 100_000_000);
let resources = await client.getAccountResources(account1.address());
let accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("100000000");
const account2 = new AptosAccount();
await faucetClient.fundAccount(account2.address(), 0);
resources = await client.getAccountResources(account2.address());
accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("0");
const builder = new TransactionBuilderRemoteABI(client, {
sender: account1.address(),
});
const rawTxn = await builder.build(
"0x1::coin::transfer",
["0x1::aptos_coin::AptosCoin"],
[account2.address(), 400]
);
const bcsTxn = AptosClient.generateBCSTransaction(account1, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
await client.waitForTransaction(transactionRes.hash);
resources = await client.getAccountResources(account2.address());
accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("400");
},
30 * 1000
);
test(
"submits multisig transaction",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const account1 = new AptosAccount();
const account2 = new AptosAccount();
const account3 = new AptosAccount();
const multiSigPublicKey = new TxnBuilderTypes.MultiEd25519PublicKey(
[
new TxnBuilderTypes.Ed25519PublicKey(account1.signingKey.publicKey),
new TxnBuilderTypes.Ed25519PublicKey(account2.signingKey.publicKey),
new TxnBuilderTypes.Ed25519PublicKey(account3.signingKey.publicKey),
],
2
);
const authKey =
TxnBuilderTypes.AuthenticationKey.fromMultiEd25519PublicKey(
multiSigPublicKey
);
const mutisigAccountAddress = authKey.derivedAddress();
await faucetClient.fundAccount(mutisigAccountAddress, 5000000);
let resources = await client.getAccountResources(mutisigAccountAddress);
let accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("5000000");
const account4 = new AptosAccount();
await faucetClient.fundAccount(account4.address(), 0);
resources = await client.getAccountResources(account4.address());
accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("0");
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString("0x1::aptos_coin::AptosCoin")
);
const entryFunctionPayload =
new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::coin",
"transfer",
[token],
[
bcsToBytes(
TxnBuilderTypes.AccountAddress.fromHex(account4.address())
),
bcsSerializeUint64(123),
]
)
);
const rawTxn = await client.generateRawTransaction(
mutisigAccountAddress,
entryFunctionPayload
);
const txnBuilder = new TransactionBuilderMultiEd25519(
(signingMessage: TxnBuilderTypes.SigningMessage) => {
const sigHexStr1 = account1.signBuffer(signingMessage);
const sigHexStr3 = account3.signBuffer(signingMessage);
const bitmap = TxnBuilderTypes.MultiEd25519Signature.createBitmap([
0, 2,
]);
const muliEd25519Sig = new TxnBuilderTypes.MultiEd25519Signature(
[
new TxnBuilderTypes.Ed25519Signature(sigHexStr1.toUint8Array()),
new TxnBuilderTypes.Ed25519Signature(sigHexStr3.toUint8Array()),
],
bitmap
);
return muliEd25519Sig;
},
multiSigPublicKey
);
const bcsTxn = txnBuilder.sign(rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
await client.waitForTransaction(transactionRes.hash);
resources = await client.getAccountResources(account4.address());
accountResource = resources.find((r) => r.type === aptosCoin);
expect((accountResource!.data as any).coin.value).toBe("123");
},
30 * 1000
);
test(
"submits json transaction simulation",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const account1 = new AptosAccount();
const account2 = new AptosAccount();
const txns1 = await faucetClient.fundAccount(account1.address(), 1000000);
const txns2 = await faucetClient.fundAccount(account2.address(), 1000000);
const tx1 = await client.getTransactionByHash(txns1[0]);
const tx2 = await client.getTransactionByHash(txns2[0]);
expect(tx1.type).toBe("user_transaction");
expect(tx2.type).toBe("user_transaction");
const checkAptosCoin = async () => {
const resources1 = await client.getAccountResources(account1.address());
const resources2 = await client.getAccountResources(account2.address());
const account1Resource = resources1.find((r) => r.type === aptosCoin);
const account2Resource = resources2.find((r) => r.type === aptosCoin);
expect(
(account1Resource!.data as { coin: { value: string } }).coin.value
).toBe("1000000");
expect(
(account2Resource!.data as { coin: { value: string } }).coin.value
).toBe("1000000");
};
await checkAptosCoin();
const payload: Gen.TransactionPayload = {
type: "entry_function_payload",
function: coinTransferFunction,
type_arguments: ["0x1::aptos_coin::AptosCoin"],
arguments: [account2.address().hex(), 100000],
};
const txnRequest = await client.generateTransaction(
account1.address(),
payload
);
const transactionRes = (
await client.simulateTransaction(account1.pubKey(), txnRequest, {
estimateGasUnitPrice: true,
estimateMaxGasAmount: true,
})
)[0];
expect(parseInt(transactionRes.gas_used, 10) > 0);
expect(transactionRes.success);
const account2AptosCoin = transactionRes.changes.filter((change) => {
if (change.type !== "write_resource") {
return false;
}
const write = change as Gen.WriteResource;
return (
write.address === account2.address().hex() &&
write.data.type === aptosCoin &&
(write.data.data as { coin: { value: string } }).coin.value ===
"1100000"
);
});
expect(account2AptosCoin).toHaveLength(1);
await checkAptosCoin();
},
30 * 1000
);
test(
"submits bcs transaction simulation",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const account1 = new AptosAccount();
const account2 = new AptosAccount();
const txns1 = await faucetClient.fundAccount(
account1.address(),
100_000_000
);
const txns2 = await faucetClient.fundAccount(
account2.address(),
100_000_000
);
const tx1 = await client.getTransactionByHash(txns1[0]);
const tx2 = await client.getTransactionByHash(txns2[0]);
expect(tx1.type).toBe("user_transaction");
expect(tx2.type).toBe("user_transaction");
const checkAptosCoin = async () => {
const resources1 = await client.getAccountResources(account1.address());
const resources2 = await client.getAccountResources(account2.address());
const account1Resource = resources1.find((r) => r.type === aptosCoin);
const account2Resource = resources2.find((r) => r.type === aptosCoin);
expect(
(account1Resource!.data as { coin: { value: string } }).coin.value
).toBe("100000000");
expect(
(account2Resource!.data as { coin: { value: string } }).coin.value
).toBe("100000000");
};
await checkAptosCoin();
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString("0x1::aptos_coin::AptosCoin")
);
const entryFunctionPayload =
new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::coin",
"transfer",
[token],
[
bcsToBytes(
TxnBuilderTypes.AccountAddress.fromHex(account2.address())
),
bcsSerializeUint64(1000),
]
)
);
const rawTxn = await client.generateRawTransaction(
account1.address(),
entryFunctionPayload
);
const bcsTxn = AptosClient.generateBCSSimulation(account1.pubKey(), rawTxn);
const transactionRes = (await client.submitBCSSimulation(bcsTxn))[0];
expect(parseInt(transactionRes.gas_used, 10) > 0);
expect(transactionRes.success);
const account2AptosCoin = transactionRes.changes.filter((change) => {
if (change.type !== "write_resource") {
return false;
}
const write = change as Gen.WriteResource;
return (
write.address === account2.address().toShortString() &&
write.data.type === aptosCoin &&
(write.data.data as { coin: { value: string } }).coin.value ===
"100001000"
);
});
expect(account2AptosCoin).toHaveLength(1);
await checkAptosCoin();
},
30 * 1000
);
test(
"submits multiagent transaction",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const tokenClient = new TokenClient(client);
const alice = new AptosAccount();
const bob = new AptosAccount();
// Fund both Alice's and Bob's Account
await faucetClient.fundAccount(alice.address(), 10000000);
await faucetClient.fundAccount(bob.address(), 10000000);
const collectionName = "AliceCollection";
const tokenName = "Alice Token";
async function ensureTxnSuccess(txnHashPromise: Promise<string>) {
const txnHash = await txnHashPromise;
const txn = await client.waitForTransactionWithResult(txnHash);
expect((txn as any)?.success).toBe(true);
}
// Create collection and token on Alice's account
await ensureTxnSuccess(
tokenClient.createCollection(
alice,
collectionName,
"Alice's simple collection",
"https://aptos.dev"
)
);
await ensureTxnSuccess(
tokenClient.createToken(
alice,
collectionName,
tokenName,
"Alice's simple token",
1,
"https://aptos.dev/img/nyan.jpeg",
1000,
alice.address(),
0,
0,
["key"],
["2"],
["int"]
)
);
const propertyVersion = 0;
const tokenId = {
token_data_id: {
creator: alice.address().hex(),
collection: collectionName,
name: tokenName,
},
property_version: `${propertyVersion}`,
};
// Transfer Token from Alice's Account to Bob's Account
await tokenClient.getCollectionData(alice.address().hex(), collectionName);
let aliceBalance = await tokenClient.getTokenForAccount(
alice.address().hex(),
tokenId
);
expect(aliceBalance.amount).toBe("1");
const txnHash = await tokenClient.directTransferToken(
alice,
bob,
alice.address(),
collectionName,
tokenName,
1,
propertyVersion
);
await client.waitForTransaction(txnHash, { checkSuccess: true });
aliceBalance = await tokenClient.getTokenForAccount(
alice.address().hex(),
tokenId
);
expect(aliceBalance.amount).toBe("0");
const bobBalance = await tokenClient.getTokenForAccount(
bob.address().hex(),
tokenId
);
expect(bobBalance.amount).toBe("1");
},
30 * 1000
);
test(
"publishes a package",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const account1 = new AptosAccount(
new HexString(
"0x883fdd67576e5fdceb370ba665b8af8856d0cae63fd808b8d16077c6b008ea8c"
).toUint8Array()
);
await faucetClient.fundAccount(account1.address(), 100_000_000);
const txnHash = await client.publishPackage(
account1,
new HexString(
// eslint-disable-next-line max-len
"084578616d706c657301000000000000000040314137344146383742383132393043323533323938383036373846304137444637393737373637383734334431434345443230413446354345334238464446388f011f8b08000000000002ff3dccc10ac2300c06e07b9ea2f46ee70b78f0a02f31c6886d74a55d5b1a51417c7713c1915c927cf9c7863ee18d2628b89239187b7ae1da32b18507758eb5e872efa42cc088217462269e60a19ceb7cc9d527bf60fcb9594da0462550f151d9b1dd2b9fbba43f6b4f82de465e302b776e90befe8f03aadd6db3351ff802f2294cdfa100000001076d6573736167658f051f8b08000000000002ff95555d6bdb30147dcfafb8ed20d8c52c1d8c3194a6ec83b0be747be8dec6108a7d9d983a5226c94dc3f07f9f2cd98e6cc769e787884857e79e73bfb4154991236c30cf055de5227e8c372ce3846c5129b646f83b01f3150a41e984109452c879774f656b8e834d2d33be3e6eb29d168aa6926d712fe423212c8e45c175dfc27979c2ea64329b910b722b518942c6682d0d6e116bb877f4ee449ea0840d53f088879a6cf5d5f409381e843cd835ea1b5023979bc57a5404ec4ac8b25aee184f72bca95d7db586f6e0d6c19486df8d21d8f23b41d0bb65592652ec226323247a6c5329b6f445ca5a9cb7291d81d96c063f37681c640ab86894c2cef03434ac4d2cb8d2b0fcfe83de2f1f1e3e7f5b12283ebc87055ccf1dc8ae58e5590c69c1618dbaf11bb0249104aa5fb311f669008bff149939eaa5e728942985525f04f89c29ad6e3a66b7163d8cc0d618215c689a9a1249028f6718ce5bb0abe94a18d33d5de762c5f293686f6be67e806a6d2616f260152a5fa12b4b23cd5675345649a1857a51708e1a6a485a11322176c0a6015c14a94883696de289cb52082e46c2e4e185a1e7ccd6b57842aa450b198d52eb75423476d06f91264284e3de6dd2cd68a71ca575f1cbb0fd5b02e60a7bc4aab819c24d5ae8c6b15f4027e5745be8b3d1990f40fd56337057d3a197a666ba97ebc980db4c3bd5c1d47887f1ebddb845a726c230193ebd6146fc09108bdde174eeca9eec718a260003ada5df2a6f7e6954ba89a931ff74fdfc2efc3dd646dc60d398717aa6a3c25736cdff34cbd8e342482c9169a44d51a44252a7a85b1d27f846d0767ca1d38fc1eaf2ae7a2423f8d2be9297d5341acc362ff6fdd119c262f10a543f9ddeec6b776be2e5a49cfc0325c63f11c007000000000300000000000000000000000000000000000000000000000000000000000000010e4170746f734672616d65776f726b00000000000000000000000000000000000000000000000000000000000000010b4170746f735374646c696200000000000000000000000000000000000000000000000000000000000000010a4d6f76655374646c696200"
).toUint8Array(),
[
new TxnBuilderTypes.Module(
new HexString(
// eslint-disable-next-line max-len
"a11ceb0b050000000c01000c020c12031e20043e04054228076ad50108bf024006ff020a108903450ace03150ce3035f0dc20404000001010102010301040105000606000007080005080700030e040106010009000100000a020300020f0404000410060000011106080106031209030106040705070105010802020c08020001030305080207080101060c010800010b0301090002070b030109000900076d657373616765076163636f756e74056572726f72056576656e74067369676e657206737472696e67124d6573736167654368616e67654576656e740d4d657373616765486f6c64657206537472696e670b6765745f6d6573736167650b7365745f6d6573736167650c66726f6d5f6d6573736167650a746f5f6d657373616765156d6573736167655f6368616e67655f6576656e74730b4576656e7448616e646c65096e6f745f666f756e640a616464726573735f6f66106e65775f6576656e745f68616e646c650a656d69745f6576656e746766284c3984add58e00d20ba272a40d33d5b4ea33c08a904254e28fdff97b9f000000000000000000000000000000000000000000000000000000000000000103080000000000000000126170746f733a3a6d657461646174615f7630310100000000000000000b454e4f5f4d4553534147451b5468657265206973206e6f206d6573736167652070726573656e740002020b08020c08020102020008020d0b030108000001000101030b0a002901030607001102270b002b0110001402010104010105210e0011030c020a022901200308050f0e000b010e00380012012d0105200b022a010c040a041000140c030a040f010b030a01120038010b010b040f0015020100010100"
).toUint8Array()
),
]
);
await client.waitForTransaction(txnHash);
const txn = await client.getTransactionByHash(txnHash);
expect((txn as any).success).toBeTruthy();
},
30 * 1000
);
test(
"rotates auth key ed25519",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const alice = new AptosAccount();
await faucetClient.fundAccount(alice.address(), 100_000_000);
const helperAccount = new AptosAccount();
const pendingTxn = await client.rotateAuthKeyEd25519(
alice,
helperAccount.signingKey.secretKey
);
await client.waitForTransaction(pendingTxn.hash);
const origAddressHex = await client.lookupOriginalAddress(
helperAccount.address()
);
// Sometimes the returned addresses do not have leading 0s. To be safe, converting hex addresses to AccountAddress
const origAddress = TxnBuilderTypes.AccountAddress.fromHex(origAddressHex);
const aliceAddress = TxnBuilderTypes.AccountAddress.fromHex(
alice.address()
);
expect(HexString.fromUint8Array(bcsToBytes(origAddress)).hex()).toBe(
HexString.fromUint8Array(bcsToBytes(aliceAddress)).hex()
);
},
30 * 1000
);
test(
"gets block by height",
async () => {
const blockHeight = 100;
const client = new AptosClient(NODE_URL);
const block = await client.getBlockByHeight(blockHeight);
expect(block.block_height).toBe(blockHeight.toString());
},
30 * 1000
);
test(
"gets block by version",
async () => {
const version = 100;
const client = new AptosClient(NODE_URL);
const block = await client.getBlockByVersion(version);
expect(parseInt(block.first_version, 10)).toBeLessThanOrEqual(version);
expect(parseInt(block.last_version, 10)).toBeGreaterThanOrEqual(version);
},
30 * 1000
);
test(
"estimates max gas amount",
async () => {
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const alice = new AptosAccount();
await faucetClient.fundAccount(alice.address(), 10000000);
const maxGasAmount = await client.estimateMaxGasAmount(alice.address());
expect(maxGasAmount).toBeGreaterThan(BigInt(0));
},
30 * 1000
);