Spending
Closed-Loop Tokens differ from standard Coin types in a key way: Token types do not have the store ability. This means you cannot store a Token inside another object or add it to a balance field the way you can with a Coin. An application that accepts a Token as payment cannot keep it.
To handle this, Token provides a spend method. When you call spend, the token is consumed and its balance moves into an ActionRequest. You then deliver the ActionRequest to the TokenPolicy (which accumulates the spent balance) or burn it directly with a TreasuryCap.
How do I spend a token?
Tokens can be spent by calling the spend method. It takes the following arguments:
// module sui::token
public fun spend<T>(token: Token<T>, ctx: &mut TxContext): ActionRequest<T>;
The spend function is defined in the sui::token module, along with the Token and ActionRequest types and the add_approval function used in the example below.
As the signature shows, the Token object is consumed. Its balance becomes the spent_balance in the ActionRequest.
What happens to a spent token?
The ActionRequest for the spend action contains the Balance of the spent Token, and it can either be confirmed with a TreasuryCap or delivered to the TokenPolicy. In the first case, the balance is burned directly in the TreasuryCap, and in the second case, it's delivered to the TokenPolicy spent_balance.
Spent balance cannot be used in any way, and it is not possible to withdraw it. The only available action is flushing - burning the spent_balance by bringing a TreasuryCap.
How do I gate the spend action?
Normally, the spend action should have at least one rule assigned to it to prevent aimless spending, and the recommended way of authorizing the spend in an application that accepts the token is to stamp it right in the function where a spend is performed. Stamping means calling token::add_approval with a rule-like witness (here GiftShop {}), which adds an approval to the ActionRequest recording that the action was performed by that rule. For example:
/// Rule-like witness to stamp the ActionRequest
public struct GiftShop has drop {}
/// Spend the token and return a Gift + ActionRequest
public fun buy_gift(
token: Token<CREDITS>,
ctx: &mut TxContext
): (Gift, ActionRequest<CREDITS>) {
// token is spent
let action_request = token::spend(token, ctx);
// stamp the ActionRequest as performed by GiftShop
token::add_approval(GiftShop {}, &mut action_request, ctx);
// return already stamped ActionRequest
(Gift { ... }, action_request)
}
The caller of buy_gift keeps the Gift and must still confirm the returned ActionRequest, either with a TreasuryCap or by delivering it to the TokenPolicy, as described in the previous section.