本文主要讲解操作 aptos cli 和 aptos sdk
Clone Aptos-core Repo
# Clone the Aptos repo.
git clone <https://github.com/aptos-labs/aptos-core.git>
# cd into aptos-core directory.
cd aptos-core
# Run the scripts/dev_setup.sh Bash script as shown below. This will prepare your developer environment.
./scripts/dev_setup.sh
# Update your current shell environment.
source ~/.cargo/env
# Skip this step if you are not installing an Aptos node.
git checkout --track origin/devnet
启动 Aptos 本地链
Using CLI to Run a Local Testnet | Aptos Docs[1
1,启动本地链
ps: 通过这个方法启动的本地链、数据都会保存在启动这条命令的当前文件夹下,以.aptos/ 文件存在
aptos node run-local-testnet --with-faucet
启动成功:
Completed generating configuration:
Log file: "/Users/greg/.aptos/testnet/validator.log"
Test dir: "/Users/greg/.aptos/testnet"
Aptos root key path: "/Users/greg/.aptos/testnet/mint.key"
Waypoint: 0:74c9d14285ec19e6bd15fbe851007ea8b66efbd772f613c191aa78721cadac25
ChainId: TESTING
REST API endpoint: 0.0.0.0:8080
FullNode network: /ip4/0.0.0.0/tcp/6181
Aptos is running, press ctrl-c to exit
Faucet is running. Faucet endpoint: 0.0.0.0:8081
启动成功后会提示 rest api 和 faucet api 的地址。后面需要把这两个信息配置在 aptos cli 环境内。
2,配置 aptos cli 环境
为了通过命令行访问和调用本地测试链,我们需要给 aptos cli 根据上面的部署信息配置 config。
PROFILE=local
aptos init --profile $PROFILE --rest-url <http://localhost:8080> --faucet-url <http://localhost:8081>
执行过程中,我们会得到如下的输出。我们可以选择输入一个秘钥,也可以默认随机生成。
Configuring for profile local
Using command line argument for rest URL <http://localhost:8080/>
Using command line argument for faucet URL <http://localhost:8081/>
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]
确认之后,会创建一个账户并使用默认数量的 token 为其注资。
No key given, generating key...
Account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1 doesn't exist, creating it and funding it with 10000 coins
Aptos is now set up for account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1! Run `aptos help` for more information about commands
{
"Result": "Success"
}
从现在开始,我们就可以通过添加--profile local命令以在本地测试网上运行它们。
ps: 这里的--profile,就像是 k8s 里的 kube-config,可以设置不同的 profile 环境,控制不同的网络。
profile 的配置,会设置执行者地址、node-rest-api、faucet-api 信息
# 列出cli控制的所有账户
aptos account list
# 为账户注资:
aptos account fund --profile $PROFILE --account $PROFILE
# 创建新的资源账户
aptos account create-resource-account --profile $PROFILE --seed 1
# 编译move合约
aptos move compile --package-dir hello_blockchain
# 部署合约
aptos move publish --package-dir hello_blockchain --named-addresses basecoin=<Your_Aptos_Account_Address> --profile local
# 调用合约
aptos move run --function-id <Move_Contract_Address>::<Moudle_Name>::<Function_Name> --profile local
# 列出指定账户的modules/resources信息
aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile local
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile local
# 合约升级
aptos move publish --upgrade-policy
`arbitrary`, `compatible`, `immutable` 对应 0,1,2
0 不做任何检查,强制替换code,
1 做兼容性检查(同样的public 函数,不能改变已有Resource的内存布局)
2 禁止升级
每次publish的时候会比较链上的policy和此次publish的policy(默认是1),
只有此次的policy小于链上的policy时才允许合约升级
部署一个简单的 Move 合约
module MyCounterAddr::MyCounter {
use std::signer;
struct Counter has key, store {
value:u64,
}
public fun init(account: &signer){
move_to(account, Counter{value:0});
}
public fun incr(account: &signer) acquires Counter {
let counter = borrow_global_mut<Counter>(signer::address_of(account));
counter.value = counter.value + 1;
}
public entry fun init_counter(account: signer){
Self::init(&account)
}
public entry fun incr_counter(account: signer) acquires Counter {
Self::incr(&account)
}
}
使用 Aptos Cli 编译、部署、调用合约
# 创建新的测试环境
aptos init --profile devtest --rest-url <http://localhost:8080> --faucet-url <http://localhost:8081>
# 编译move合约
aptos move compile --package-dir my-counter
# 部署合约
# 例如:aptos move publish --package-dir my-counter --named-addresses basecoin=0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664 --profile devtest
aptos move publish --package-dir my-counter --named-addresses basecoin=<Your_Aptos_Account_Address> --profile devtest
# 调用合约
# 例如:
# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::init_counter --profile devtest
# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::incr_counter --profile devtest
aptos move run --function-id <Move_Contract_Address>::<Moudle_Name>::<Function_Name> --profile devtest
# 列出指定账户的modules/resources信息
aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile devtest
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile devtest
Aptos SDK 调用 Move 合约
编译好合约之后,我们可以通过 sdk 调用我们的合约。
我们可以选择通过 sdk 部署合约,也可以通过 sdk 调用 move 合约。
通过 sdk 部署合约
当我们编译完成之后,会在 move 合约文件夹下生成 build/ 文件夹
我们需要把 my-counter/build/Examples/bytecode_modules/MyCounter.mv 文件 copy 到SDK脚本下。
aptos move compile --package-dir my-counter
cp MyCounter.mv my-counter-sdk-demo/
1,部署合约相关的 sdk 代码
/** Publish a new module to the blockchain within the specified account */
export async function publishModule(accountFrom: AptosAccount, moduleHex: string): Promise<string> {
const moudleBundlePayload = new TxnBuilderTypes.TransactionPayloadModuleBundle(
new TxnBuilderTypes.ModuleBundle([new TxnBuilderTypes.Module(new HexString(moduleHex).toUint8Array())]),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
moudleBundlePayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
return transactionRes.hash;
}
2,通过 SDK 发送交易
这里,我们以 my-counter 合约中的init_counter 和 incr_counter 为例。
构造两个方法用于调用这两个方法,从而实现客户端调用 init 和 incr 的功能。
async function initCounter(contractAddress: string, accountFrom: AptosAccount): Promise<string> {
const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
TxnBuilderTypes.ScriptFunction.natural(
`${contractAddress}::MyCounter`, // 合约地址::合约名称
"init_counter", // script 函数方法
[],
[],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
scriptFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
return transactionRes.hash;
}
async function incrCounter(contractAddress: string, accountFrom: AptosAccount): Promise<string> {
const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
TxnBuilderTypes.ScriptFunction.natural(
`${contractAddress}::MyCounter`,
"incr_counter",
[],
[],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
scriptFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
return transactionRes.hash;
}
3,通过 SDK 获取账户里的资源信息。
resource 是存放在所属的账户地址下的,我们可以根据 account 地址,查询相关的 resource 信息。
getCounter()方法其实就是获取 my-counter 下的 **Counter **资源。
async function getCounter(contractAddress: string, accountAddress: MaybeHexString): Promise<string> {
try {
const resource = await client.getAccountResource(
accountAddress.toString(),
`${contractAddress}::MyCounter::Counter`,
);
return (resource as any).data["value"];
} catch (_) {
return "";
}
}
其实这个效果就类似 sdk 里的
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf
最终的主函数
async function main() {
assert(process.argv.length == 3, "Expecting an argument that points to the helloblockchain module");
const contractAddress = "0x173d51b1d50614b03d0c18ffcd958309042a9c0579b6b21fc9efeb48cdf6e0b0"; // 指定之前部署的合约地址
const bob = new AptosAccount(); // 创建一个测试地址 Bob
console.log("\n=== Addresses ===");
console.log(`Bob: ${bob.address()}`);
await faucetClient.fundAccount(bob.address(), 5_000); // 给 Bob 地址空投5000个测试token
console.log("\n=== Initial Balances ===");
console.log(`Bob: ${await accountBalance(bob.address())}`);
await new Promise<void>((resolve) => {
readline.question(
"Update the module with Alice's address, build, copy to the provided path, and press enter.",
() => {
resolve();
readline.close();
},
);
});
const modulePath = process.argv[2];
const moduleHex = fs.readFileSync(modulePath).toString("hex");
console.log('Init Counter Moudle.');
let txHash = await initCounter(contractAddress, bob); // 在bob下init Counter资源,此时bob下的Counter的value为0.
await client.waitForTransaction(txHash);
console.log("\n=== Testing Bob Get Counter Value ===");
console.log(`Initial value: ${await getCounter(contractAddress, bob.address())}`);
console.log('========== Incr Counter Value, 1th ==========');
txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为1.
console.log(txHash);
await client.waitForTransaction(txHash);
await Sleep(100);
console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。
console.log('========== Incr Counter Value, 2th ==========');
txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为2.
console.log(txHash);
await client.waitForTransaction(txHash);
await Sleep(100);
console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。
console.log('========== Incr Counter Value, 3th ==========');
txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为3.
console.log(txHash);
await client.waitForTransaction(txHash);
await Sleep(100);
console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。
}
if (require.main === module) {
main().then((resp) => console.log(resp));
}
执行效果
执行成功,在这里通过 SDK,给一个随机生成的账户 init 了 Counter 资源(Counter=0),然后 incr 了三次,所以最后 Counter 的 Value 为 3。
my-counter 相关代码:https://github.com/99Kies/Aptos-Move-Dapp
参考资料
[1]
Using CLI to Run a Local Testnet | Aptos Docs: https://aptos.dev/nodes/local-testnet/using-cli-to-run-a-local-testnet
来源:DAOrayaki