Loyalty Tokens
Using the Sui Closed-Loop Token standard, you can create tokens that are valid only for a specific service, like an airline that wants to grant tokens to frequent flyers to purchase tickets or upgrades.
The following example demonstrates the creation of a loyalty token that bearers can use to make purchases in a digital gift shop. Use the comments in the code to follow the logic of the example.
examples/move/token/sources/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 std::option;
    use sui::transfer;
    use sui::object::{Self, UID};
    use sui::coin::{Self, TreasuryCap};
    use sui::tx_context::{Self, TxContext};
    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.
    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.
    struct GiftShop has drop {}
    /// The Gift object - can be purchased for 10 tokens.
    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.
    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 (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 req = token::spend(token, ctx);
        // only required because we've set this rule
        token::add_approval(GiftShop {}, &mut req, ctx);
        (gift, req)
    }
}