Module sui::token
The Token module which implements a Closed Loop Token with a configurable policy. The policy is defined by a set of rules that must be satisfied for an action to be performed on the token.
The module is designed to be used with a TreasuryCap to allow for minting
and burning of the
Tokens. And can act as a replacement / extension or a
companion to existing open-loop (Coin) systems.
Module: sui::balance sui::coin sui::token
Main type: Balance<T> Coin<T> Token<T>
Capability: Supply<T> <----> TreasuryCap<T> <----> TreasuryCap<T>
Abilities: store key + store key
The Token system allows for fine-grained control over the actions performed on the token. And hence it is highly suitable for applications that require control over the currency which a simple open-loop system can't provide.
- Struct Token
- Struct TokenPolicyCap
- Struct TokenPolicy
- Struct ActionRequest
- Struct RuleKey
- Struct TokenPolicyCreated
- Constants
- Function new_policy
- Function share_policy
- Function transfer
- Function spend
- Function to_coin
- Function from_coin
- Function join
- Function split
- Function zero
- Function destroy_zero
- Function keep
- Function new_request
- Function confirm_request
- Function confirm_request_mut
- Function confirm_with_policy_cap
- Function confirm_with_treasury_cap
- Function add_approval
- Function add_rule_config
- Function rule_config
- Function rule_config_mut
- Function remove_rule_config
- Function has_rule_config
- Function has_rule_config_with_type
- Function allow
- Function disallow
- Function add_rule_for_action
- Function remove_rule_for_action
- Function mint
- Function burn
- Function flush
- Function is_allowed
- Function rules
- Function spent_balance
- Function value
- Function transfer_action
- Function spend_action
- Function to_coin_action
- Function from_coin_action
- Function action
- Function amount
- Function sender
- Function recipient
- Function approvals
- Function spent
- Function key
use std::address;
use std::ascii;
use std::bcs;
use std::option;
use std::string;
use std::type_name;
use std::vector;
use sui::accumulator;
use sui::accumulator_metadata;
use sui::accumulator_settlement;
use sui::address;
use sui::bag;
use sui::balance;
use sui::bcs;
use sui::coin;
use sui::config;
use sui::deny_list;
use sui::dynamic_field;
use sui::dynamic_object_field;
use sui::event;
use sui::funds_accumulator;
use sui::hash;
use sui::hex;
use sui::object;
use sui::party;
use sui::table;
use sui::transfer;
use sui::tx_context;
use sui::types;
use sui::url;
use sui::vec_map;
use sui::vec_set;
Struct Token
A single
Token with Balance inside. Can only be owned by an address,
and actions performed on it must be confirmed in a matching TokenPolicy.
public struct Token<phantom T> has key
Fields
-
id: sui::object::UID -
balance: sui::balance::Balance<T> -
The Balance of the
.Token
Struct TokenPolicyCap
A Capability that manages a single
TokenPolicy specified in the for
field. Created together with TokenPolicy in the new function.
public struct TokenPolicyCap<phantom T> has key, store
Fields
-
id: sui::object::UID -
for: sui::object::ID
Struct TokenPolicy
TokenPolicy represents a set of rules that define what actions can be
performed on a Token and which Rules must be satisfied for the
action to succeed.
- For the sake of availability,
is aTokenPolicy
-only object.key - Each
is managed by a matchingTokenPolicy
.TokenPolicyCap - For an action to become available, there needs to be a record in the
VecMap. To allow an action to be performed freely, there's anrules
function that can be called by theallow
owner.TokenPolicyCap
public struct TokenPolicy<phantom T> has key
Fields
-
id: sui::object::UID -
spent_balance: sui::balance::Balance<T> -
The balance that is effectively spent by the user on the "spend"
action. However, actual decrease of the supply can only be done by
the
TreasuryCapowner when
is called. This balance is effectively spent and cannot be accessed by anyone but theflushTreasuryCapowner. -
rules: sui::vec_map::VecMap<std::string::String, sui::vec_set::VecSet<std::type_name::TypeName>> -
The set of rules that define what actions can be performed on the
token. For each "action" there's a set of Rules that must be
satisfied for the
to be confirmed.ActionRequest
Struct ActionRequest
A request to perform an "Action" on a token. Stores the information about the action to be performed and must be consumed by the
confirm_request
or confirm_request_mut functions when the Rules are satisfied.
public struct ActionRequest<phantom T>
Fields
-
name: std::string::String -
Name of the Action to look up in the Policy. Name can be one of the
default actions:
,transfer
,spend
,to_coin
or a custom action.from_coin -
amount: u64 - Amount is present in all of the txs
-
sender: address - Sender is a permanent field always
-
recipient: std::option::Option<address> -
Recipient is only available in
action.transfer -
spent_balance: std::option::Option<sui::balance::Balance<T>> -
The balance to be "spent" in the
, only available in theTokenPolicy
action.spend -
approvals: sui::vec_set::VecSet<std::type_name::TypeName> -
Collected approvals (stamps) from completed
Rules. They're matched against
to determine if the request can be confirmed.TokenPolicy.rules
Struct RuleKey
Dynamic field key for the
TokenPolicy to store the Config for a
specific action Rule. There can be only one configuration per
Rule per TokenPolicy.
public struct RuleKey<phantom T> has copy, drop, store
Fields
-
is_protected: bool
Struct TokenPolicyCreated
An event emitted when a
TokenPolicy is created and shared. Because
TokenPolicy can only be shared (and potentially frozen in the future),
we emit this event in the share_policy function and mark it as mutable.
public struct TokenPolicyCreated<phantom T> has copy, drop
Fields
-
id: sui::object::ID -
ID of the
that was created.TokenPolicy -
is_mutable: bool -
Whether the
is "shared" (mutable) or "frozen" (immutable) - TBD.TokenPolicy
Constants
The action is not allowed (defined) in the policy.
const EUnknownAction: u64 = 0;
The rule was not approved.
const ENotApproved: u64 = 1;
Trying to perform an admin action with a wrong cap.
const ENotAuthorized: u64 = 2;
The balance is too low to perform the action.
const EBalanceTooLow: u64 = 3;
The balance is not zero.
const ENotZero: u64 = 4;
The balance is not zero when trying to confirm with TransferPolicyCap.
const ECantConsumeBalance: u64 = 5;
Rule is trying to access a missing config (with type).
const ENoConfig: u64 = 6;
Using
confirm_request_mut without spent_balance. Immutable version
of the function must be used instead.
const EUseImmutableConfirm: u64 = 7;
A Tag for the
spend action.
const SPEND: vector<u8> = vector[115, 112, 101, 110, 100];
A Tag for the
transfer action.
const TRANSFER: vector<u8> = vector[116, 114, 97, 110, 115, 102, 101, 114];
A Tag for the
to_coin action.
const TO_COIN: vector<u8> = vector[116, 111, 95, 99, 111, 105, 110];
A Tag for the
from_coin action.
const FROM_COIN: vector<u8> = vector[102, 114, 111, 109, 95, 99, 111, 105, 110];
Function new_policy
Create a new
TokenPolicy and a matching TokenPolicyCap.
The TokenPolicy must then be shared using the share_policy method.
TreasuryCap guarantees full ownership over the currency, and is unique,
hence it is safe to use it for authorization.
public fun new_policy<T>(_treasury_cap: &sui::coin::TreasuryCap<T>, ctx: &mut sui::tx_context::TxContext): (sui::token::TokenPolicy<T>, sui::token::TokenPolicyCap<T>)
Implementation
public fun new_policy<T>(
_treasury_cap: &TreasuryCap<T>,
ctx: &mut TxContext,
): (TokenPolicy<T>, TokenPolicyCap<T>) {
let policy = TokenPolicy {
id: object::new(ctx),
spent_balance: balance::zero(),
rules: vec_map::empty(),
};
let cap = TokenPolicyCap {
id: object::new(ctx),
for: object::id(&policy),
};
(policy, cap)
}
Function share_policy
Share the
TokenPolicy. Due to key-only restriction, it must be
shared after initialization.
public fun share_policy<T>(policy: sui::token::TokenPolicy<T>)
Implementation
public fun share_policy<T>(policy: TokenPolicy<T>) {
event::emit(TokenPolicyCreated<T> {
id: object::id(&policy),
is_mutable: true,
});
transfer::share_object(policy)
}
Function transfer
Transfer a
Token to a recipient. Creates an ActionRequest for the
"transfer" action. The ActionRequest contains the recipient field
to be used in verification.
public fun transfer<T>(t: sui::token::Token<T>, recipient: address, ctx: &mut sui::tx_context::TxContext): sui::token::ActionRequest<T>
Implementation
public fun transfer<T>(t: Token<T>, recipient: address, ctx: &mut TxContext): ActionRequest<T> {
let amount = t.balance.value();
transfer::transfer(t, recipient);
new_request(
transfer_action(),
amount,
option::some(recipient),
option::none(),
ctx,
)
}
Function spend
Spend a
Token by unwrapping it and storing the Balance in the
ActionRequest for the "spend" action. The ActionRequest contains
the spent_balance field to be used in verification.
Spend action requires
confirm_request_mut to be called to confirm the
request and join the spent balance with the TokenPolicy.spent_balance.
public fun spend<T>(t: sui::token::Token<T>, ctx: &mut sui::tx_context::TxContext): sui::token::ActionRequest<T>
Implementation
public fun spend<T>(t: Token<T>, ctx: &mut TxContext): ActionRequest<T> {
let Token { id, balance } = t;
id.delete();
new_request(
spend_action(),
balance.value(),
option::none(),
option::some(balance),
ctx,
)
}
Function to_coin
Convert
Token into an open Coin. Creates an ActionRequest for the
"to_coin" action.
public fun to_coin<T>(t: sui::token::Token<T>, ctx: &mut sui::tx_context::TxContext): (sui::coin::Coin<T>, sui::token::ActionRequest<T>)
Implementation
public fun to_coin<T>(t: Token<T>, ctx: &mut TxContext): (Coin<T>, ActionRequest<T>) {
let Token { id, balance } = t;
let amount = balance.value();
id.delete();
(
balance.into_coin(ctx),
new_request(
to_coin_action(),
amount,
option::none(),
option::none(),
ctx,
),
)
}
Function from_coin
Convert an open Coin into a
Token. Creates an ActionRequest for
the "from_coin" action.
public fun from_coin<T>(coin: sui::coin::Coin<T>, ctx: &mut sui::tx_context::TxContext): (sui::token::Token<T>, sui::token::ActionRequest<T>)
Implementation
public fun from_coin<T>(coin: Coin<T>, ctx: &mut TxContext): (Token<T>, ActionRequest<T>) {
let amount = coin.value();
let token = Token {
id: object::new(ctx),
balance: coin.into_balance(),
};
(
token,
new_request(
from_coin_action(),
amount,
option::none(),
option::none(),
ctx,
),
)
}
Function join
Join two
Tokens into one, always available.
public fun join<T>(token: &mut sui::token::Token<T>, another: sui::token::Token<T>)
Function split
Split a
Token with amount.
Aborts if the Token.balance is lower than amount.
public fun split<T>(token: &mut sui::token::Token<T>, amount: u64, ctx: &mut sui::tx_context::TxContext): sui::token::Token<T>
Function zero
Create a zero
Token.
public fun zero<T>(ctx: &mut sui::tx_context::TxContext): sui::token::Token<T>
Implementation
public fun zero<T>(ctx: &mut TxContext): Token<T> {
Token {
id: object::new(ctx),
balance: balance::zero(),
}
}
Function destroy_zero
Destroy an empty
Token, fails if the balance is non-zero.
Aborts if the Token.balance is not zero.
public fun destroy_zero<T>(token: sui::token::Token<T>)
Function keep
Transfer the
Token to the transaction sender.
public fun keep<T>(token: sui::token::Token<T>, ctx: &mut sui::tx_context::TxContext)
Function new_request
Create a new
ActionRequest.
Publicly available method to allow for custom actions.
public fun new_request<T>(name: std::string::String, amount: u64, recipient: std::option::Option<address>, spent_balance: std::option::Option<sui::balance::Balance<T>>, ctx: &sui::tx_context::TxContext): sui::token::ActionRequest<T>
Implementation
public fun new_request<T>(
name: String,
amount: u64,
recipient: Option<address>,
spent_balance: Option<Balance<T>>,
ctx: &TxContext,
): ActionRequest<T> {
ActionRequest {
name,
amount,
recipient,
spent_balance,
sender: ctx.sender(),
approvals: vec_set::empty(),
}
}
Function confirm_request
Confirm the request against the
TokenPolicy and return the parameters
of the request: (Name, Amount, Sender, Recipient).
Cannot be used for
spend and similar actions that deliver spent_balance
to the TokenPolicy. For those actions use confirm_request_mut.
Aborts if:
- the action is not allowed (missing record in
)rules - action contains
(usespent_balance
)confirm_request_mut - the
does not meet theActionRequest
rules for the actionTokenPolicy
public fun confirm_request<T>(policy: &sui::token::TokenPolicy<T>, request: sui::token::ActionRequest<T>, _ctx: &mut sui::tx_context::TxContext): (std::string::String, u64, address, std::option::Option<address>)
Implementation
public fun confirm_request<T>(
policy: &TokenPolicy<T>,
request: ActionRequest<T>,
_ctx: &mut TxContext,
): (String, u64, address, Option<address>) {
assert!(request.spent_balance.is_none(), ECantConsumeBalance);
assert!(policy.rules.contains(&request.name), EUnknownAction);
let ActionRequest {
name,
approvals,
spent_balance,
amount,
sender,
recipient,
} = request;
spent_balance.destroy_none();
let rules = &(*policy.rules.get(&name)).into_keys();
let rules_len = rules.length();
let mut i = 0;
while (i < rules_len) {
let rule = &rules[i];
assert!(approvals.contains(rule), ENotApproved);
i = i + 1;
};
(name, amount, sender, recipient)
}
Function confirm_request_mut
Confirm the request against the
TokenPolicy and return the parameters
of the request: (Name, Amount, Sender, Recipient).
Unlike
confirm_request this function requires mutable access to the
TokenPolicy and must be used on spend action. After dealing with the
spent balance it calls confirm_request internally.
See
confirm_request for the list of abort conditions.
public fun confirm_request_mut<T>(policy: &mut sui::token::TokenPolicy<T>, request: sui::token::ActionRequest<T>, ctx: &mut sui::tx_context::TxContext): (std::string::String, u64, address, std::option::Option<address>)
Implementation
public fun confirm_request_mut<T>(
policy: &mut TokenPolicy<T>,
mut request: ActionRequest<T>,
ctx: &mut TxContext,
): (String, u64, address, Option<address>) {
assert!(policy.rules.contains(&request.name), EUnknownAction);
assert!(request.spent_balance.is_some(), EUseImmutableConfirm);
policy.spent_balance.join(request.spent_balance.extract());
confirm_request(policy, request, ctx)
}
Function confirm_with_policy_cap
Confirm an
ActionRequest as the TokenPolicyCap owner. This function
allows TokenPolicy owner to perform Capability-gated actions ignoring
the ruleset specified in the TokenPolicy.
Aborts if request contains
spent_balance due to inability of the
TokenPolicyCap to decrease supply. For scenarios like this a
TreasuryCap is required (see confirm_with_treasury_cap).
public fun confirm_with_policy_cap<T>(_policy_cap: &sui::token::TokenPolicyCap<T>, request: sui::token::ActionRequest<T>, _ctx: &mut sui::tx_context::TxContext): (std::string::String, u64, address, std::option::Option<address>)
Implementation
public fun confirm_with_policy_cap<T>(
_policy_cap: &TokenPolicyCap<T>,
request: ActionRequest<T>,
_ctx: &mut TxContext,
): (String, u64, address, Option<address>) {
assert!(request.spent_balance.is_none(), ECantConsumeBalance);
let ActionRequest {
name,
amount,
sender,
recipient,
approvals: _,
spent_balance,
} = request;
spent_balance.destroy_none();
(name, amount, sender, recipient)
}
Function confirm_with_treasury_cap
Confirm an
ActionRequest as the TreasuryCap owner. This function
allows TreasuryCap owner to perform Capability-gated actions ignoring
the ruleset specified in the TokenPolicy.
Unlike
confirm_with_policy_cap this function allows spent_balance
to be consumed, decreasing the total_supply of the Token.
public fun confirm_with_treasury_cap<T>(treasury_cap: &mut sui::coin::TreasuryCap<T>, request: sui::token::ActionRequest<T>, _ctx: &mut sui::tx_context::TxContext): (std::string::String, u64, address, std::option::Option<address>)
Implementation
public fun confirm_with_treasury_cap<T>(
treasury_cap: &mut TreasuryCap<T>,
request: ActionRequest<T>,
_ctx: &mut TxContext,
): (String, u64, address, Option<address>) {
let ActionRequest {
name,
amount,
sender,
recipient,
approvals: _,
spent_balance,
} = request;
if (spent_balance.is_some()) {
treasury_cap.supply_mut().decrease_supply(spent_balance.destroy_some());
} else {
spent_balance.destroy_none();
};
(name, amount, sender, recipient)
}