前言
Coming Chat 开发了基于 aptos 链的红包合约,并实现了 app 内的 aptos 红包功能。本文将介绍如何部署此红包合约,并使用 aptos 命令行工具与红包合约交互。
合约代码概览
可以通过此链接 https://github.com/coming-chat/red-packet/blob/main/sources/red_packet.move 查看红包合约代码。
合约有 7 个 entry 函数:
initialze(owner, benecifary, admin)
create(operator, count, total_balance)
open(operator, id, luck_accounts, balances)
close(operator, id)
set_admin(operator, admin)
set_fee_point(owner, new_fee_point)
set_base_prepaid_fee(owner, new_base_prepaid)
逻辑上,合约把 signer 分为 4 类:
owner
:合约部署者,合约部署后用来初始化合约benecifary
:合约受益人,红包手续费,未领取红包等优先给受益人admin
:合约的管理者,open/close
等需要使用admin
账号处理。others
:其他用户可以创建红包。
一个红包的生命周期:
- 用户发送
create
红包的交易,红包被创建 - 链下业务:抢红包
admin
调用open
,传入抢到红包的地址和对应的balances
admin
调用close
关闭红包。
创建红包时需要注意最低金额和最高可抢数量:
const MAX_COUNT: u64 = 1000; // 最高数量
const MIN_BALANCE: u64 = 10000; // 最近金额 0.0001 APT(decimals=8)
合约部署
首先确保安装最新的 aptos 命令行工具。安装方法参考官方文档。
后续命令里凡是出现地址的地方,替换为自己的地址即可,注意,是地址(Address),不是公钥(Public Key)。
下载代码:
git clone https://github.com/coming-chat/red-packet.git
初始化项目
cd red-packet
aptos init
之后你可以把你的公私钥和地址填入到 ${your project path}/.aptos/config.yml
里。
确保你的账户有足够的 AptosCoin
。在 dev 环境下,可以使用下面的命令领取水龙头测试币。
# account 参数是地址
aptos account fund-with-faucet --account 9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1 --amount 50000
# 查询 resource
aptos account list --account 9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1
编译合约
# address 参数是地址,不是公钥,使用公钥会报错
aptos move compile --named-addresses RedPacket=0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1
编译过程中如果出现类似 Unable to find package manifest for 'AptosFramework'
的错误,记得修改项目下的 Move.toml
文件里 dependencies
的配置项,把 AptosFramework
和 AptosStdlib
指向你本地的 aptos-framework
和 aptos-stdlib
目录。aptos 库代码从官方仓库拉取。
部署合约
# address 参数是地址,不是公钥,使用公钥会报错
aptos move publish --named-addresses RedPacket=0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1
合约交互
命令行下执行 aptos move run
,发送初始化合约受益人和管理员的交易。这里我把我的地址设置为受益人和管理员了,在生产环境中,受益人和管理员应使用不同的账户。
aptos move run \
--function-id '0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1::red_packet::initialze' \
--args 'address:0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1' 'address:0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1'
创建红包:此示例创建可以被两个人抢的红包,创建空包传入的 amount
是 10000
。但由于合约默认收取 250/10000
的手续费,实际进入到红包的 amount
是 9750
。
aptos move run \
--function-id '0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1::red_packet::create' \
--args 'u64:2' 'u64:10000'
当我们再次用 aptos account list --account 9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1
查看资产的时候,可以到看到最下面有红包资产。用户创建的红包资产都在合约下,以方便 admin
开红包。我们刚创建的红包的 key
是 1
。
"data": [
{
"key": "1", // 红包ID
"value": {
"coin": {
"value": "9750"
},
"remain_count": "2"
}
}
]
开红包:开红包的时候,需要保证传入的 luck_accounts
数量和 balances
数量相同,且小于红包的 remain_count
;balances
的总数小于红包余额。
因为开红包传入的参数类型是 vector<address>
和 vector<u64>
,aptos move run
命令还不支持此类参数,所以我用 go 写了段简单的代码来完成开红包。源码在这里,是基于 coming-chat/go-aptos 和 coming-chat/wallet-SDK 开发的。
chain := aptos.NewChainWithRestUrl(base.TestNetUrl)
payload := &aptostypes.Payload{
Type: aptostypes.EntryFunctionPayload,
Function: "0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1::red_packet::open",
TypeArguments: []string{},
Arguments: []interface{}{
"1",
[]string{"0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1", "0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1"},
[]string{"1000", "8750"},
},
}
data, err := json.Marshal(payload)
base.PanicError(err)
hash, err := chain.SubmitTransactionPayload(account, data) // account 是 admin 账号
关闭红包:把红包里剩余的 coin
转到受益人账户,并设置 remain_count
为 0
。在业务逻辑上,可以把超过若干时长的红包关闭掉,防止出现僵尸资产。
aptos move run \
--function-id '0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1::red_packet::close' \
--args 'u64:1'
合约 Event
目前红包 create/open/close
操作都会触发 event
。event
的结构:
struct RedPacketEvent has drop, store {
id: u64, // 红包ID
event_type: u8, // 操作 0:create 1:open 2:close
remain_count: u64, // 剩余的可抢数量
remain_balance: u64 // 剩余的 balance
}
我们可以通过监控(定时获取)events
列表,来发现红包创建等操作。通过 events
列表,可以把交易 version
跟红包 id
关联起来。
curl --request GET \
--url 'https://fullnode.devnet.aptoslabs.com/v1/accounts/0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1/events/0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1::red_packet::RedPackets/events?start=0&limit=10' \
--header 'Content-Type: application/json'
[
{
"version": "36412603",
"key": "0x04000000000000009fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1",
"sequence_number": "0",
"type": "0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1::red_packet::RedPacketEvent",
"data": {
"event_type": 0,
"id": "1",
"remain_balance": "9750",
"remain_count": "2"
}
}
]
其他注意事项
管理员开红包消耗的 gas
跟 luck_accounts
成正比。在 dev 环境测试 luck_accounts
达到 1000
时,gas
消耗为 3000
左右。在生产环境中需注意监控管理员 balance
是否足够。
抢红包的时候需要注意,用户必须 register
过对应 Coin
。目前合约仅支持 AptosCoin
原生币,则只需要抢红包的时候确认账户有 AptosCoin
即可。如果没有则使用水龙头领一下。
创建红包时传入的 balance
数量,会收取 balance / 10000 * 250
作为手续费(这里保证了手续费是 250
的倍数),比如 create
时传入的 balance
为 10000
,则最终只有 9750
进入到红包。如果想要红包有 10000
,则需要倒推输入的 balance
为 10250
。根据红包金额倒推输入 balance
的逻辑:
// amount 红包里最终有的金额数量,feePoint 为手续费比例,合约默认为 250
// 返回值为用户调用 create 时候需要传入的 balance
func calcTotal(amount uint64, feePoint uint64) uint64 {
if feePoint == 0 {
feePoint = 250
}
if amount < 10000 {
return amount
}
fee := amount / 10000 * feePoint
left := uint64(0)
right := amount / feePoint
for left <= right {
center := (left + right) / 2
tmpFee := center*feePoint + fee
tmpTotal := tmpFee + amount
tmpC := tmpTotal - tmpTotal/10000*feePoint
if tmpC > amount {
right = center - 1
} else if tmpC < amount {
left = center + 1
} else {
return tmpTotal
}
}
return amount
}
总结
抢红包的逻辑虽然比较简单,但为了保证合约长期有效运行,还是需要不少的细节设计。比如 合约三种角色 owner/admin/benecifary
的设计保证了权限安全和资产安全;动态调整手续费比例能够适应不同的网络 gas 环境。
合约的使用需要良好的载体 — ComingChat APP 里现已集成了 Aptos 红包功能,在 Aptos 的高速网络下,发/收 红包有非常棒的丝滑体验。