Aptos合约开发新手指南
本文旨在帮助你开始在Aptos区块链上编写智能合约。我们将解释过程中的关键概念,并逐步编写一个“代币锁定期”程序。

一键发币: Aptos | X Layer | SUI | SOL | BNB | ETH | BASE | ARB | OP | Polygon | Avalanche | 用AI学区块链开发
本博客旨在帮助你开始在Aptos区块链上编写智能合约。博客将解释过程中的关键概念。我们将逐步编写一个“代币锁定期”程序。“代币锁定期”程序是Mokshya Protocol的第一个开源合同。您可以在这里查看完整代码。
Mokshya是一个开源协议,在Aptos区块链上构建智能合约、SDK和开发工具。
1、安装和Aptos
Aptos的文档在安装方面非常清晰有关更详细和清晰的入门指导,请从这里开始。 你可以在这里学习更多与Aptos相关的概念。
2、什么是 代币 锁定期?
Aptos将常用的和使用的代币定义为“硬币”,其原生代币“Aptos”在aptos_coin模块中定义。你可以通过coin模块创建、铸造、冻结、转移和销毁自己的代币。
在Aptos中,模块就像智能合约。我们可以创建和发布“模块”。这些模块是具有逻辑的独立实体,可以从前端调用以执行各种交易。
现在,假设Alice给了价值1000个代币(硬币)的工钱,名为Mokshya “MOK”,他要在不同的时间表上支付给Bob如下:
- 1月1日,200 MOK
- 1月30日,300 MOK
- 2月12日,400 MOK
- 2月21日,100 MOK
Bob需要确保他的付款会在这些时候进行。Alice担心如果他在开始时就支付所有款项,Bob可能无法完成工作。
一个解决方案是找到双方都信任的人来按要求进行计划的付款。更好的解决方案是使用一个合同来执行这些计划的付款。这就是代币锁定期的基本概念。
代币锁定期是从一方到另一方的无信任计划付款。
3、代币锁定期是如何工作的?
Alice将所有的计划和释放金额发送到Aptos区块链并保存。同时,Alice将1000 MOK代币存入一个资源账户,该账户作为无信任的托管账户。当计划到来时,Bob可以提取指定的金额。从这我们清楚我们需要两个主要功能:
- 创建锁定期:定义计划和付款,接收者和存款总额。function create_vesting
- 接收付款:验证接收者并在当天支付应付金额。function **release_fund.**在下一步中,我们将实现这些功能
4、初始化和导入依赖项
首先,创建一个名为token-vesting的文件夹。现在,在终端中进入token-vesting文件夹,使用以下命令:
aptos move init --name token-vesting
如果您打开token-vesting文件夹,将看到以下自动生成的内容:

这是Aptos模块或智能合约开发的开发结构。在sources文件夹中,有模块。一个单独的模块可以分解成更小的子模块,它们都在sources中。Move.toml类似于包管理器Cargo.toml。它定义了模块名称和各种依赖项。
为了方便,将Move.toml替换为以下内容:
[package]
name = 'token-vesting'
version = '1.0.0'
[addresses]
token_vesting = "_"
Std = "0x1"
aptos_std = "0x1"
[dependencies]
AptosFramework = { local = "../../aptos-core/aptos-move/framework/aptos-framework"}
将 local = "../../aptos-core/aptos-move/framework/aptos-framework"
替换为你的aptos-framework所在的本地文件夹。
现在,我们已准备好转向编写智能合约。在sources文件夹中,创建一个名为“token-vesting.move”的文件。
5、定义
在开始时,我们使用module标识符定义程序,模块名为“token_vesting”。模块名必须与Move.toml中地址段的名称匹配。如前所述,我们可以在token_vesting模块内编写多个子模块,在这种情况下,我们有一个名为vesting的单个子模块。
module token_vesting::vesting {
}
在模块内部,我们定义模块中所需的全部依赖项,
use std::signer;
use aptos_framework::account;
use std::vector;
use aptos_framework::managed_coin;
use aptos_framework::coin;
use aptos_std::type_info;
use aptos_std::simple_map::{Self, SimpleMap};
6、定义结构
我们需要保存关于代币锁定期合同的数据,为此Aptos提供了struct选项,可用于定义各种数据结构。在我们的情况下,
// 所有用于Vesting的信息
struct VestingSchedule has key,store
{
sender: address,
receiver: address,
coin_type:address,
release_times:vector<u64>, //解锁的时间
release_amounts:vector<u64>, //对应解锁的金额
total_amount:u64, //所有释放金额的总和
resource_cap: account::SignerCapability, // Signer
released_amount:u64, //已释放金额的总和
}
在Aptos中,address是每个帐户的唯一标识符。我们需要sender、receiver和coin_type的地址。如果你来自Solana的背景,可以将其视为“代币mint地址”。对于我们的案例,需要MOK代币的coin_type。
release_times 是升序排列的UNIX时间戳向量。对于Alex和Bob的情况,对应的UNIX时间戳为(将年份设为2023,时间为00:00)
- 1月1日–1672510500
- 1月30日–1675016100
- 2月12日–1676139300
- 2月21日–1676916900
release_amounts 是对应时间的预定金额。
total_amount 是所有释放金额的总和,在我们的例子中是1000 MOK。
resource_cap 表示托管或资源账户的签名者能力(稍后会进一步解释)
released_amount 用于记录Bob已经提取的金额
Move中的能力定义了数据结构的边界。在我们的例子中,结构VestingSchedule具有存储和键的能力。因此,它可以存储在账户中。
// 存储种子和相应资源账户地址的映射
struct VestingCap has key {
vestingMap: SimpleMap< vector<u8>,address>,
}
这个结构用于保存种子和相应的资源地址。这里使用了简单映射,之前从aptos_std模块导入。
以下是手动编写的错误,自我解释:
//错误
const ENO_INSUFFICIENT_FUND:u64=0;
const ENO_NO_VESTING:u64=1;
const ENO_SENDER_MISMATCH:u64=2;
const ENO_RECEIVER_MISMATCH:u64=3;
const ENO_WRONG_SENDER:u64=4;
const ENO_WRONG_RECEIVER:u64=5;
每个错误都定义了一个u64数字,以便在执行事务时更容易识别错误。
7、创建锁定期函数
在Aptos中,不同的标识符用于根据函数授予的访问权限定义函数。由于我们的函数需要由用户Ale调用,它被定义为入口函数。在Aptos move中,交易签署人作为入口函数的输入&signer,在我们的情况下是Alice。由于Move要求使用的结构已经定义,我们获取VestingCap。CoinType在我们的情况下是Mokshya代币。
public entry fun create_vesting<CoinType>(
account: &signer,
receiver: address,
release_amounts:vector<u64>,
release_times:vector<u64>,
total_amount:u64,
seeds: vector<u8>
)acquires VestingCap {
}
首先,我们需要生成一个资源账户,该账户将作为托管账户即vesting。使用Alice的账户和种子创建资源账户。!exists(account_addr) 命令验证VestingCap struct是否已经在Alice的账户中存在,move_to 如果Alice的账户中不存在该结构,则将其移动到Alice的账户中。borrow_global_mut 将结构的可变引用带入Alice的账户,并将种子和相应的vesting地址添加到简单映射中以便将来访问。
let account_addr = signer::address_of(account);
let (vesting, vesting_cap) = account::create_resource_account(account, seeds); //资源账户
let vesting_address = signer::address_of(&vesting);
if (!exists<VestingCap>(account_addr)) {
move_to(account, VestingCap { vestingMap: simple_map::create() })
};
let maps = borrow_global_mut<VestingCap>(account_addr);
simple_map::add(&mut maps.vestingMap, seeds,vesting_address);
vesting_signer_from_cap 是资源账户- vesting 的签名者能力。它是托管 vesting 的 signer 形式。
let vesting_signer_from_cap = account::create_signer_with_capability(&vesting_cap);
下面是利用之前导入的向量模块的一行代码。我们验证release_amount,release_times的长度以及release_amounts是否等于总金额。
let length_of_schedule = vector::length(&release_amounts);
let length_of_times = vector::length(&release_times);
assert!(length_of_schedule==length_of_times,ENO_INSUFFICIENT_FUND);
let i=0;
let total_amount_required=0;
while ( i < length_of_schedule )
{
let tmp = *vector::borrow(&release_amounts,i);
total_amount_required=total_amount_required+tmp;
i=i+1;
};
assert!(total_amount_required==total_amount,ENO_INSUFFICIENT_FUND);
如前所述,我们通过辅助函数推导出coin_address,并将所有信息保存在资源账户- vesting 中。
let released_amount=0;
let coin_address = coin_address<CoinType>();
move_to(&vesting_signer_from_cap, VestingSchedule{
sender:account_addr,
receiver,
coin_type:coin_address,
release_times,
release_amounts,
total_amount,
resource_cap:vesting_cap,
released_amount,
});
辅助函数用于推导coin_address的定义如下。
/// 一个返回CoinType地址的辅助函数。
fun coin_address<CoinType>(): address {
let type_info = type_info::type_of<CoinType>();
type_info::account_address(&type_info)
}
fun 名称(输入): 数据类型 { 表达式1; 表达式2 }
在函数定义中,冒号后的数据类型是返回类型。返回数据可以在函数内部不带分号(表达式2 是返回类型)。
现在剩下的唯一事情就是将代币从Alice转移到vesting托管账户。首先,我们需要在资源账户中注册代币。这一步使得账户只能接收账户想要的代币。下一步,将MOK代币转移到vesting资源账户
managed_coin::register<CoinType>(&vesting_signer_from_cap);
coin::transfer<CoinType>(account, vesting_address, total_amount);
现在,函数已经准备好了,你可以编译模块了。
module token_vesting::vesting {
............
............
............
}
首先,让我们创建一个账户并指定dev-net作为我们的集群
aptos init
现在,Aptos CLI已为账户20634774e3d40bf68fa86101723f2bc36c7b57bc5220e401475f2f1b27377a10 设置为默认配置文件!运行aptos — help
了解有关命令的更多信息
{
“Result”: “Success”
}
现在,您可以使用获得的地址来编译您的代码
aptos move compile --named-addresses token_vesting="0xaddress_obtained_in_above_command"
8、释放资金功能
该函数由Bob调用以获取其锁定的资金。由于该函数需要有关两个结构的信息,因此在函数定义时需要获取它们。
public entry fun release_fund<CoinType>(
receiver: &signer,
sender: address,
seeds: vector<u8>
)acquires VestingSchedule,VestingCap{
在以下几行中,我们从vesting资源账户借用VestingSchedule及其signer-capability以释放资金。同样,验证发送者和接收者。
let receiver_addr = signer::address_of(receiver);
assert!(exists<VestingCap>(sender), ENO_NO_VESTING);
let maps = borrow_global<VestingCap>(sender);
let vesting_address = *simple_map::borrow(&maps.vestingMap, &seeds);
assert!(exists<VestingSchedule>(vesting_address), ENO_NO_VESTING);
let vesting_data = borrow_global_mut<VestingSchedule>(vesting_address);
let vesting_signer_from_cap = account::create_signer_with_capability(&vesting_data.resource_cap);
assert!(vesting_data.sender==sender,ENO_SENDER_MISMATCH);
assert!(vesting_data.receiver==receiver_addr,ENO_RECEIVER_MISMATCH);
在这里,现在的时间戳是从aptos-framework中得出的。现在,计算接收者到目前为止可以收到的资金数额。所以,如果日期是2月12日,应释放的金额应为900 MOKS
let length_of_schedule = vector::length(&vesting_data.release_amounts);
let i=0;
let amount_to_be_released=0;
let now = aptos_framework::timestamp::now_seconds();
while (i < length_of_schedule)
{
let tmp_amount = *vector::borrow(&vesting_data.release_amounts,i);
let tmp_time = *vector::borrow(&vesting_data.release_times,i);
if (tmp_time<=now)
{
amount_to_be_released=amount_to_be_released+tmp_amount;
};
i=i+1;
};
amount_to_be_released=amount_to_be_released-vesting_data.released_amount;
但是,如果Bob在1月30日已经释放了500 MOKS,那么此时应扣除已释放的金额。因此,应释放的金额将是400。
现在,如前所述,MOKS代币已在Bob的账户中注册,然后从vesting - 资源账户转移到Bob的地址。
if (!coin::is_account_registered<CoinType>(receiver_addr))
{
managed_coin::register<CoinType>(receiver);
};
coin::transfer<CoinType>(&vesting_signer_from_cap,receiver_addr,amount_to_be_released);
vesting_data.released_amount=vesting_data.released_amount+amount_to_be_released;
现在,amount_to_be_released 添加到 released_amount 中。因此,下次他只能访问剩余的资金。
9、发布模块
现在,我们准备好发布模块。在终端中使用以下命令:
aptos move publish --named-addresses token_vesting="0xaddress_obtained_in_above_command"
10、与模块交互
你可以在repo中的tests文件夹中找到测试代码。
创建锁定期
//alice 是账户1,bob 是账户2
await faucetClient.fundAccount(account1.address(), 1000000000);//空投
//时间和金额
const now = Math.floor(Date.now() / 1000)
//任何离散金额和对应时间
//可以提供以获得多种付款计划
const release_amount =[10000, 50000, 10000, 30000];
const release_time_increment =[ 3, 20, 30];
var release_time:BigInt[]=[BigInt(now)]
release_time_increment.forEach((item) => {
let val=BigInt(now+item);
release_time.push(val);
});
const create_vesting_payloads = {
type: "entry_function_payload",
function: pid+"::vesting::create_vesting",
type_arguments: ["0x1::aptos_coin::AptosCoin"],
arguments: [account2.address(),release_amount,release_time,100000,"xyz"],
};
let txnRequest = await client.generateTransaction(account1.address(), create_vesting_payloads);
let bcsTxn = AptosClient.generateBCSTransaction(account1, txnRequest);
await client.submitSignedBCSTransaction(bcsTxn);
释放资金
await faucetClient.fundAccount(account2.address(), 1000000000);//空投
//接收者按需分配资金
const create_getfunds_payloads = {
type: "entry_function_payload",
function: pid+"::vesting::release_fund",
type_arguments: ["0x1::aptos_coin::AptosCoin"],
arguments: [account1.address(),"xyz"],
};
let txnRequest = await client.generateTransaction(account2.address(), create_getfunds_payloads);
let bcsTxn = AptosClient.generateBCSTransaction(account2, txnRequest);
await client.submitSignedBCSTransaction(bcsTxn);
11、结束语
这只是你在Aptos上编写move模块旅程的第一步。欢迎你在Mokshya Protocol的开源解决方案中做出贡献,以在Aptos区块链上进行智能合约开发之旅。
原文链接:Write Your First Smart Contract On Aptos — A Step-by-Step Guide
DefiPlot翻译整理,转载请标明出处
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。