로열티 토큰
Sui Closed-Loop Token standard를 사용하면, 항공사가 항공권이나 업그레이드를 구매하도록 상용 고객에게 token을 지급하고 싶어 하는 경우처럼, 특정 service에 대해서만 유효한 token을 생성할 수 있다.
다음 예시는 bearer가 디지털 gift shop에서 구매하는 데 사용할 수 있는 loyalty token의 생성을 보여준다.
Example
loyalty token 예시는 Closed Loop Token standard로 생성된 loyalty token을 보여준다.
이 예시를 구현한다고 가정하면, Admin은 service 사용자의 loyalty에 대한 보상으로 LOYALTY token을 보낸다.
예시는 holder가 LOYALTY token을 사용해 Gift를 살 수 있는 GiftShop을 만든다.
loyalty.move source file에는 loyalty token을 만드는 examples::loyalty module code가 들어 있다.
이 module에는 coin을 생성하는 One-Time Witness(OTW)가 포함되어 있으며(이름은 module과 같은 LOYALTY), drop ability만 가지고 field는 없다.
이것이 OTW의 특성이며, LOYALTY type이 단일 instance를 갖도록 보장한다.
/// The OTW for the Token / Coin.
public struct LOYALTY has drop {}
module의 init 함수는 LOYALTY OTW를 사용해 token을 생성한다.
모든 init 함수는 package publish event 중에만 실행된다.
initializer 함수는 앞서 정의한 OTW LOYALTY type을 사용해 create_currency를 호출한다.
이 함수는 또한 policy를 정의하고 policy capability와 treasury capability 둘 다 publish event와 연결된 address로 보낸다.
이 transferable capability의 holder는 새로운 LOYALTY token을 mint하고 그 policy를 수정할 수 있다.
#[allow(deprecated_usage)]
fun init(otw: LOYALTY, ctx: &mut TxContext) {
let (treasury_cap, coin_metadata) = coin::create_currency(
otw,
0, // no decimals
b"LOY", // symbol
b"Loyalty Token", // name
b"Token for Loyalty", // description
option::none(), // url
ctx,
);
let (mut policy, policy_cap) = token::new_policy(&treasury_cap, ctx);
token::add_rule_for_action<LOYALTY, GiftShop>(
&mut policy,
&policy_cap,
token::spend_action(),
ctx,
);
token::share_policy(policy);
transfer::public_freeze_object(coin_metadata);
transfer::public_transfer(policy_cap, tx_context::sender(ctx));
transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
}
LOYALTY minting 함수의 이름은 reward_user이다.
앞서 언급했듯이 TreasuryCap holder는 이 함수를 호출해 새로운 loyalty token을 mint하고 원하는 address로 보낼 수 있다.
이 함수는 token::mint 함수를 사용해 token을 생성하고 token::transfer를 사용해 의도한 recipient에게 보낸다.
public fun reward_user(
cap: &mut TreasuryCap<LOYALTY>,
amount: u64,
recipient: address,
ctx: &mut TxContext,
) {
let token = token::mint(cap, amount, ctx);
let req = token::transfer(token, recipient, ctx);
token::confirm_with_treasury_cap(cap, req, ctx);
}
마지막으로 예시에는 LOYALTY token을 Gift type으로 교환하는 redemption을 처리하기 위한 buy_a_gift 함수가 포함되어 있다.
이 함수는 gift 가격이 사용된 loyalty token 수와 일치하는지 확인한 다음 token::spend 함수를 사용해 treasury bookkeeping을 처리한다.
public fun buy_a_gift(token: Token<LOYALTY>, ctx: &mut TxContext): (Gift, ActionRequest<LOYALTY>) {
assert!(token::value(&token) == GIFT_PRICE, EIncorrectAmount);
let gift = Gift { id: object::new(ctx) };
let mut req = token::spend(token, ctx);
token::add_approval(GiftShop {}, &mut req, ctx);
(gift, req)
}
Complete code
comment를 포함한 이 예시의 complete source를 표시하려면 toggle을 사용하거나, GitHub에서 project source를 보려면 Related links 섹션의 링크를 사용한다.
loyalty.move
loyalty.move/// This module illustrates a Closed Loop Loyalty Token. The `Token` is sent to
/// users as a reward for their loyalty by the application Admin. The `Token`
/// can be used to buy a `Gift` in the shop.
///
/// Actions:
/// - spend - spend the token in the shop
module examples::loyalty;
use sui::coin::{Self, TreasuryCap};
use sui::token::{Self, ActionRequest, Token};
/// Token amount does not match the `GIFT_PRICE`.
const EIncorrectAmount: u64 = 0;
/// The price for the `Gift`.
const GIFT_PRICE: u64 = 10;
/// The OTW for the Token / Coin.
public struct LOYALTY has drop {}
/// This is the Rule requirement for the `GiftShop`. The Rules don't need
/// to be separate applications, some rules make sense to be part of the
/// application itself, like this one.
public struct GiftShop has drop {}
/// The Gift object - can be purchased for 10 tokens.
public struct Gift has key, store {
id: UID,
}
// Create a new LOYALTY currency, create a `TokenPolicy` for it and allow
// everyone to spend `Token`s if they were `reward`ed.
#[allow(deprecated_usage)]
fun init(otw: LOYALTY, ctx: &mut TxContext) {
let (treasury_cap, coin_metadata) = coin::create_currency(
otw,
0, // no decimals
b"LOY", // symbol
b"Loyalty Token", // name
b"Token for Loyalty", // description
option::none(), // url
ctx,
);
let (mut policy, policy_cap) = token::new_policy(&treasury_cap, ctx);
// but we constrain spend by this shop:
token::add_rule_for_action<LOYALTY, GiftShop>(
&mut policy,
&policy_cap,
token::spend_action(),
ctx,
);
token::share_policy(policy);
transfer::public_freeze_object(coin_metadata);
transfer::public_transfer(policy_cap, tx_context::sender(ctx));
transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
}
/// Handy function to reward users. Can be called by the application admin
/// to reward users for their loyalty :)
///
/// `Mint` is available to the holder of the `TreasuryCap` by default and
/// hence does not need to be confirmed; however, the `transfer` action
/// does require a confirmation and can be confirmed with `TreasuryCap`.
public fun reward_user(
cap: &mut TreasuryCap<LOYALTY>,
amount: u64,
recipient: address,
ctx: &mut TxContext,
) {
let token = token::mint(cap, amount, ctx);
let req = token::transfer(token, recipient, ctx);
token::confirm_with_treasury_cap(cap, req, ctx);
}
/// Buy a gift for 10 tokens. The `Gift` is received, and the `Token` is
/// spent (stored in the `ActionRequest`'s `burned_balance` field).
public fun buy_a_gift(token: Token<LOYALTY>, ctx: &mut TxContext): (Gift, ActionRequest<LOYALTY>) {
assert!(token::value(&token) == GIFT_PRICE, EIncorrectAmount);
let gift = Gift { id: object::new(ctx) };
let mut req = token::spend(token, ctx);
// only required because we've set this rule
token::add_approval(GiftShop {}, &mut req, ctx);
(gift, req)
}