SUI Move合约学习与实践——抢红包合约(sui-red-packet)
SUI Move
的抢红包合约weather-oracle
大气压数据计算获得https://github.com/movefuns/sui-red-packet
成员变量说明:
sender
:红包创建者amount
:红包个数left_amount
:红包剩余个数coin_type
:红包代币类型coin_amount
:红包代币余额original_amount
:初始红包余额claimer_addresses
:红包领取人地址列表specified_recipient
:定向红包指定接收人地址列表 struct RedPacket<phantom T> has key, store {
id: UID,
sender: address,
amount: u64,
left_amount: u64,
coin_type: String,
coin_amount: Balance<T>,
original_amount: u64,
claimer_addresses: vector<address>,
specified_recipient: Option<vector<address>>
}
red_packet_id
:红包IDsender
:红包创建者地址amount
:红包个数coin_type
:代币类型coin_amount
:代币数量 struct NewRedPacket<phantom T> has copy, drop {
red_packet_id: ID,
sender: address,
amount: u64,
coin_type: String,
coin_amount: u64,
}
claim_red_packet_id
:领取红包IDclaimer
:红包领取者地址claim_amount
:领取红包数量claim_coin_type
:领取红包代币类型 struct ClaimRedPacket<phantom T> has copy, drop {
claim_red_packet_id: ID,
claimer: address,
claim_amount: u64,
claim_coin_type: String,
}
send_new_red_packet
)调用该接口会创建红包共享对象(RedPacket
)
该红包共享对象会包含代币类型名称,获取方法是:
let coin_type = type_name::get<T>();
let coin_type_string = *type_name::borrow_string(&coin_type);
此外红包共享对象的成员变量还会包括红包创建者(sender
)、红包个数(amount
)、红包总金额(coin_amount
)、红包指定接收人地址(specified_recipient
)
抛出红包创建事件(NewRedPacket
)
public entry fun send_new_red_packet<T>(
amount: u64,
coin_amount: Coin<T>,
specified_recipient: Option<vector<address>>,
ctx: &mut TxContext,
) {
let sender = tx_context::sender(ctx);
let id = object::new(ctx);
let red_packet_id = object::uid_to_inner(&id);
let coin_amount_num = coin::value(&coin_amount);
let coin_amount = coin::into_balance(coin_amount);
let coin_type = type_name::get<T>();
let coin_type_string = *type_name::borrow_string(&coin_type);
event::emit(NewRedPacket<T> {
red_packet_id,
sender,
amount,
coin_type: coin_type_string,
coin_amount: coin_amount_num,
});
let red_packet = RedPacket<T> {
id,
sender,
amount,
left_amount:amount,
coin_type: coin_type_string,
coin_amount,
original_amount: coin_amount_num,
claimer_addresses: vector::empty<address>(),
specified_recipient,
};
transfer::share_object(red_packet);
}
注:因为命令行调用,无法传递空值,对做合约接口做了修改,去掉了可选入参
specified_recipient
。https://docs.sui.io/references/sui-api
修改后的合约代码如下:
public entry fun send_new_red_packet<T>( amount: u64, coin_amount: Coin<T>, ctx: &mut TxContext, ) { let sender = tx_context::sender(ctx); let id = object::new(ctx); let red_packet_id = object::uid_to_inner(&id); let coin_amount_num = coin::value(&coin_amount); let coin_amount = coin::into_balance(coin_amount); let coin_type = type_name::get<T>(); let coin_type_string = *type_name::borrow_string(&coin_type); event::emit(NewRedPacket<T> { red_packet_id, sender, amount, coin_type: coin_type_string, coin_amount: coin_amount_num, }); let red_packet = RedPacket<T> { id, sender, amount, left_amount:amount, coin_type: coin_type_string, coin_amount, original_amount: coin_amount_num, claimer_addresses: vector::empty<address>(), specified_recipient: option::none(), }; transfer::share_object(red_packet); }
claim_red_packet
)EAlreadyClaimed
ENotInSpecifiedRecipients
max
),算法是:(红包余额 / 红包剩余数量) × 2
max
的大小的实际领取金额(claim_amount
),计算该金额是采用天气预言机获取随机数据源(具体请参考:《使用Sui天气预言机获取全球实时天气数据》、《Sui Weather Oracle》)ClaimRedPacket
)public entry fun claim_red_packet<T>(red_packet:&mut RedPacket<T>,weather_oracle: &WeatherOracle, clock: &Clock, ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);
assert!(!vector::contains(&red_packet.claimer_addresses, &sender), EAlreadyClaimed);
if(!option::is_none(&red_packet.specified_recipient)) {
let specified = option::borrow(&red_packet.specified_recipient);
assert!(vector::contains(specified, &sender), ENotInSpecifiedRecipients);
};
let left_value = balance::value(&red_packet.coin_amount);
let coin_type = type_name::get<T>();
let coin_type_string = *type_name::borrow_string(&coin_type);
let _log_claim_amount: u64 = 0;
if (red_packet.left_amount == 1) {
red_packet.left_amount = red_packet.left_amount - 1;
let coin = coin::take(&mut red_packet.coin_amount, left_value, ctx);
transfer::public_transfer(coin, sender);
_log_claim_amount = left_value;
} else {
let max = (left_value / red_packet.left_amount) * 2;
let claim_amount = get_random(weather_oracle,max,clock,ctx);
let claim_split = balance::split(&mut red_packet.coin_amount, claim_amount);
let claim_value = coin::from_balance(claim_split, ctx);
red_packet.left_amount = red_packet.left_amount - 1;
transfer::public_transfer(claim_value, sender);
_log_claim_amount = claim_amount;
};
vector::push_back(&mut red_packet.claimer_addresses,sender);
event::emit(ClaimRedPacket<T> {
claim_red_packet_id: object::uid_to_inner(&red_packet.id),
claimer: sender,
claim_amount: _log_claim_amount,
claim_coin_type: coin_type_string,
})
}
random_pressure_p
和random_pressure_l
(位置编号与城市名称的对应关系见:https://github.com/MystenLabs/apps/tree/main/weather-oracle#oracles)blake2b256
编码,再将其经由bcs
编码后转成u64
整型max
进行取模运算后,获得随机数,该随机数机转账红包金额 fun get_random(weather_oracle: &WeatherOracle, max: u64, clock: &Clock,ctx: &TxContext):u64{
let sender = tx_context::sender(ctx);
let tx_digest = tx_context::digest(ctx);
let random_pressure_p = oracle::weather::city_weather_oracle_pressure(weather_oracle, 2988507);
let random_pressure_l = oracle::weather::city_weather_oracle_pressure(weather_oracle, 88319);
let random_vector = vector::empty<u8>();
vector::append(&mut random_vector, address::to_bytes(copy sender));
vector::append(&mut random_vector, u32_to_ascii(random_pressure_p));
vector::append(&mut random_vector, u32_to_ascii(random_pressure_l));
vector::append(&mut random_vector, u64_to_ascii(clock::timestamp_ms(clock)));
vector::append(&mut random_vector, *copy tx_digest);
let temp1 = blake2b256(&random_vector);
let random_num_ex = bcs::peel_u64(&mut bcs::new(temp1));
let random_value = ((random_num_ex % max) as u64);
debug::print(&random_value);
random_value
}
别名 | 地址 | 角色 |
---|---|---|
Jason | 0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a |
红包创建者 |
Alice | 0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19 |
红包领取者1 |
Bob | 0xf2e6ffef7d0543e258d4c47a53d6fa9872de4630cc186950accbd83415b009f0 |
红包领取者2 |
export JASON=0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a
export ALICE=0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19
export BOB=0xf2e6ffef7d0543e258d4c47a53d6fa9872de4630cc186950accbd83415b009f0
切换到Jason账号
因
weather-oracle
版本较老,需要使用较老的sui
才能编译,还需要跳过依赖检查
export GAS_BUDGET=100000000
sui_1.19.1 client publish --gas-budget $GAS_BUDGET --skip-dependency-verification
部署合约虽然创建了
AdminCap
和Publisher
合约中并没有使用:
export PACKAGE_ID=0xe5417558cf7edc87840fef347f294dc0fa7bdcd82c043e630c504d233c6b4784
send_new_red_packet
)export GAS_BUDGET=10000000
export COUNT=3 # 红包数量3个
# 找Jason名下2个大MIST对象,一个用于支付gas,一个用于拆分出指定MIST数量的coin对象
sui client gas --json | jq '.[] | select(.mistBalance > 100000) | .gasCoinId' -r > output.txt
GAS=$(sed -n '1p' output.txt)
SPLIT_COIN=$(sed -n '2p' output.txt)
# 拆分出10000 MIST,存入红包中
export COIN=`sui client split-coin --coin-id $SPLIT_COIN --amounts 10000 --gas $GAS --gas-budget $GAS_BUDGET --json | jq -r '.objectChanges[] | select(.objectType=="0x2::coin::Coin<0x2::sui::SUI>" and .type=="created") | .objectId'`
sui client call --function send_new_red_packet --package $PACKAGE_ID --module red_packet --type-args 0x2::sui::SUI --args $COUNT $COIN --gas-budget $GAS_BUDGET
export RED_POCKET=0xe4713158775a3ca05eb55099076be009f94e8f4fcdf354a34b108949c0f52ab7
sui client object $RED_POCKET
claim_red_packet
)export WEATHER_ORACLE=0x1aedcca0b67b891c64ca113fce87f89835236b4c77294ba7e2db534ad49a58dc
sui client switch --address alice
sui client call --function claim_red_packet --package $PACKAGE_ID --module red_packet --type-args 0x2::sui::SUI --args $RED_POCKET $WEATHER_ORACLE 0x6 --gas-budget 10000000
Alice领取后,剩余红包数量(
left_amount
)、红包余额(coin_amount
)、领取人列表(claimer_addresses
)都响应发生了变化。
sui client object $RED_POCKET
Error executing transaction: Failure {
error: "MoveAbort(MoveLocation { module: ModuleId { address: e5417558cf7edc87840fef347f294dc0fa7bdcd82c043e630c504d233c6b4784, name: Identifier(\"red_packet\") }, function: 2, instruction: 20, function_name: Some(\"claim_red_packet\") }, 1) in command 0",
}
sui client switch --address bob
sui client call --function claim_red_packet --package $PACKAGE_ID --module red_packet --type-args 0x2::sui::SUI --args $RED_POCKET $WEATHER_ORACLE 0x6 --gas-budget 10000000
Bob领取后,剩余红包数量(
left_amount
)、红包余额(coin_amount
)、领取人列表(claimer_addresses
)都继续发生了变化。
sui client object $RED_POCKET
切换到Jason,领取最后一个红包。
因为只剩最后一个红包了,最后一个用户将会一次性全部领取。
sui client switch --address jason
sui client call --function claim_red_packet --package $PACKAGE_ID --module red_packet --type-args 0x2::sui::SUI --args $RED_POCKET $WEATHER_ORACLE 0x6 --gas-budget 10000000
最后一个红包被领取后,剩余红包数量(
left_amount
)归零、红包余额(coin_amount
)归零、领取人列表(claimer_addresses
)出现3个领取地址。
sui client object $RED_POCKET
将文件:src/App.tsx
中的PACKAGE_ID
改成我们自己刚才合约发布的PACKAGE_ID
:
const PACKAGE_ID =
"0xe5417558cf7edc87840fef347f294dc0fa7bdcd82c043e630c504d233c6b4784";
因我们将创建红包接口(
send_new_red_packet
)接口中的可选入参specified_recipient
去掉了,方便命令行调试,前端代码中也需要相应将该字段去掉。
发布地址:https://github-movefuns-sui-red-packet.vercel.app/
send_new_red_packet
)创建红包后,得到新的抢红包连接:https://github-movefuns-sui-red-packet.vercel.app/?redpacket=0xa5b7871a39c7b03adfad3a123e8c47e38ade54d609eb47ec01bc404c1f384ff3
claim_red_packet
)如果要体验该红包Dapp
请访问:https://sui-red-packet.vercel.app/
欢迎关注微信公众号:Move中文,开启你的 Sui Move 之旅!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!