Action Request
The main difference between a Token and a Coin is that Token does not allow transfers, conversions, or spending by default. However, there's an authorization mechanism that allows these actions to be performed. This mechanism is called an ActionRequest
. Token creator can choose to allow / disallow any of the actions (see RequestConfirmation).
Protected Actions
Token has 4 protected actions which create an ActionRequest
:
Function | Action Name | Description | Special Fields in ActionRequest |
---|---|---|---|
token::from_coin | "from_coin" | Convert a Coin into a Token | - |
token::to_coin | "to_coin" | Convert a Token into a Coin | - |
token::transfer | "transfer" | Transfer a Token to a recipient | Contains "recipient" field |
token::spend | "spend" | Spend a Token | Contains "spent_balance" field |
ActionRequest Structure
ActionRequest is defined in the sui::token
module and contains the following fields:
name
- name of the performed action, standard ones are "transfer", "spend", "to_coin" and "from_coin", and it is possible to create custom actions.amount
- the amount of the token that is being transferred, spent, converted, etc.sender
- the account that initiated the actionrecipient
- the account that receives the token in "transfer" action (can be used for custom actions)spent_balance
- the balance of a spent Token in the "spend" action (can be utilized in custom actions)
These fields can be used by so-called "Rules" to determine whether the action should be allowed or not. Rules are custom modules which implement restriction logic. See "Rules" section for more details.
An example of a function creating an ActionRequest
:
// module: sui::token
public fun transfer<T>(
t: Token<T>, recipient: address, ctx: &mut TxContext
): ActionRequest<T>;
Request Confirmation
There are 3 ways to confirm an ActionRequest
:
- With a
TreasuryCap
- token creator (or an application storing the TreasuryCap) can confirm any request by callingtoken::confirm_with_treasury_cap
function. This method is useful for applications that store the TreasuryCap and implement custom logic; it also allows the Token creator tomint
andtransfer
tokens bypassing the restrictions. - With a
TokenPolicy
- token creator can create a sharedTokenPolicy
and set up allowed actions and requirements for each action. This way any application or a wallet would know which actions can be considered "public" and would be able to perform them. - With a
TokenPolicyCap
- the capability managing theTokenPolicy
can also be used to confirm requests. This can be useful for applications that have theTreasuryCap
wrapped and inaccessible; and some admin action needs to be authorized by the Token creator.
TokenPolicyCap cannot be used to confirm "spend" requests (see Spending)
Confirming with TreasuryCap
TreasuryCap can be used to confirm any ActionRequest for the Token. It's useful for admin actions (eg mint and transfer) as well as for simple applications that don't require a TokenPolicy and wrap the TreasuryCap into the main object.
The method signature for the token::confirm_with_treasury_cap
function is as follows:
// module: sui::token
public fun confirm_with_treasury_cap<T>(
treasury_cap: &mut TreasuryCap<T>,
request: ActionRequest<T>,
ctx: &mut TxContext
): (String, u64, address, Option<address>);
An example of a transaction implemented in TypeScript with sui.js, confirming an ActionRequest
with a TreasuryCap. Here the TreasuryCap is owned by the admin account and is used to mint and confirm the transfer request for the token:
let tx = new TransactionBlock();
let tokenType = '0x....::my_token::MY_TOKEN';
let treasuryCapArg = tx.object('0x....');
// mint a 10 tokens using the TreasuryCap
let token = tx.moveCall({
target: '0x2::token::mint',
arguments: [ treasuryCapArg, tx.pure.u64(10) ],
typeArguments: [ tokenType ],
});
// transfer the token to a recipient; receive an `ActionRequest`
let request = tx.moveCall({
target: '0x2::token::transfer',
arguments: [ token, tx.pure.address('0x...') ],
typeArguments: [ tokenType ],
});
// confirm the request with the TreasuryCap
tx.moveCall({
target: '0x2::token::confirm_with_treasury_cap',
arguments: [ treasuryCapArg, request ],
typeArguments: [ tokenType ],
});
// submit the transaction
// ...
Confirming with TokenPolicy
TokenPolicy is a way of enabling certain actions network-wide. Once shared, the TokenPolicy
is available to everyone. Hence, it can be used by wallets or any other clients to confirm allowed operations.
The method signature for the token::confirm_request
function is as follows:
// module: sui::token
public fun confirm_request<T>(
treasury_cap: &TokenPolicy<T>,
request: ActionRequest<T>,
ctx: &mut TxContext
): (String, u64, address, Option<address>);
If it's a "spend" request, use the confirm_request_mut
function instead.
An example of a client transfer request confirmation in JavaScript:
let tx = new TransactionBlock();
let tokenType = '0x....::my_token::MY_TOKEN';
let myTokenArg = tx.object('0x...token_object');
let receiverArg = tx.pure.address('0x...receiver');
let tokenPolicyArg = tx.object('0x...token_policy');
let request = tx.moveCall({
target: '0x2::token::transfer',
arguments: [ myTokenArg, receiverArg ],
typeArguments: [ tokenType ],
});
// expecting the `TokenPolicy` to have the `transfer` operation allowed
tx.moveCall({
target: '0x2::token::confirm_request',
arguments: [ tokenPolicyArg, request ],
typeArguments: [ tokenType ],
});
// submit the transaction
// ...
Confirming with TokenPolicyCap
TokenPolicyCap can also be used to confirm action requests. This way can come in handy when the TreasuryCap
is wrapped in another object, and TokenPolicy
does not allow certain action or has Rules that make the default way of confirming impossible.
TokenPolicyCap cannot be used to confirm "spend" requests (see Spending)
// module: sui::token
public fun confirm_with_policy_cap<T>(
token_policy_cap: &TokenPolicyCap<T>,
request: ActionRequest<T>,
ctx: &mut TxContext
): (String, u64, address, Option<address>);
An example of a client transfer request confirmation in JavaScript:
let tx = new TransactionBlock();
let tokenType = '0x....::my_token::MY_TOKEN';
let myTokenArg = tx.object('0x...token_object');
let receiverArg = tx.pure.address('0x...receiver');
let tokenPolicyCapArg = tx.object('0x...token_policy_cap');
let request = tx.moveCall({
target: '0x2::token::transfer',
arguments: [ myTokenArg, receiverArg ],
typeArguments: [ tokenType ],
});
// confirming the request with the TokenPolicyCap
tx.moveCall({
target: '0x2::token::confirm_with_policy_cap',
arguments: [ tokenPolicyCapArg, request ],
typeArguments: [ tokenType ],
});
// submit the transaction
// ...
Approving Actions
ActionRequest
s can collect "approvals" - witness stamps from applications or Rules. They carry the confirmation that a certain module or a rule has approved the action. This mechanic allows gating actions behind certain requirements.
Signature for the token::add_approval
function is as follows:
// module: sui::token
public fun add_approval<T, W: drop>(
_t: W, request: &mut ActionRequest<T>, _ctx: &mut TxContext
);
Approvals are mostly used for Rules. However, they can carry confirmations from any module.
Creating a Custom Request
A new ActionRequest
can be created by anyone using the token::new_request
function. It can be used to create custom actions and rules, not necessarily related to the Token
itself.
Because ActionRequest
can be created freely for any type T, they can't be used as a "proof" of the action. Their purpose is authorization, not proof.
The method signature for the token::new_request
function is as follows:
public fun new_request<T>(
name: vector<u8>,
amount: u64,
recipient: option<address>,
spent_balance: option<Balance<T>>,
ctx: &mut TxContext
): ActionRequest<T>;