Aptos 红包合约使用

前言

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 的配置项,把 AptosFrameworkAptosStdlib 指向你本地的 aptos-frameworkaptos-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'

创建红包:此示例创建可以被两个人抢的红包,创建空包传入的 amount10000。但由于合约默认收取 250/10000 的手续费,实际进入到红包的 amount9750

aptos move run \
--function-id '0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1::red_packet::create' \
--args 'u64:2' 'u64:10000'

当我们再次用 aptos account list --account 9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1 查看资产的时候,可以到看到最下面有红包资产。用户创建的红包资产都在合约下,以方便 admin 开红包。我们刚创建的红包的 key1

"data": [
  {
    "key": "1",   // 红包ID
    "value": {
      "coin": {
        "value": "9750"
      },
      "remain_count": "2"
    }
  }
]

开红包:开红包的时候,需要保证传入的 luck_accounts 数量和 balances 数量相同,且小于红包的 remain_countbalances 的总数小于红包余额。

因为开红包传入的参数类型是 vector<address>vector<u64>aptos move run 命令还不支持此类参数,所以我用 go 写了段简单的代码来完成开红包。源码在这里,是基于 coming-chat/go-aptoscoming-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_count0。在业务逻辑上,可以把超过若干时长的红包关闭掉,防止出现僵尸资产。

aptos move run \
--function-id '0x9fecf7c6ad1fc0e5337c6d64443cda47b41f61b556a698193646ce0b8917cbe1::red_packet::close' \
--args 'u64:1'

合约 Event

目前红包 create/open/close 操作都会触发 eventevent 的结构:

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"
    }
  }
]

其他注意事项

管理员开红包消耗的 gasluck_accounts 成正比。在 dev 环境测试 luck_accounts 达到 1000 时,gas 消耗为 3000 左右。在生产环境中需注意监控管理员 balance 是否足够。

抢红包的时候需要注意,用户必须 register 过对应 Coin。目前合约仅支持 AptosCoin 原生币,则只需要抢红包的时候确认账户有 AptosCoin 即可。如果没有则使用水龙头领一下。

创建红包时传入的 balance 数量,会收取 balance / 10000 * 250 作为手续费(这里保证了手续费是 250 的倍数),比如 create 时传入的 balance10000,则最终只有 9750 进入到红包。如果想要红包有 10000,则需要倒推输入的 balance10250。根据红包金额倒推输入 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 的高速网络下,发/收 红包有非常棒的丝滑体验。