Skip to main content

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:

FunctionAction NameDescriptionSpecial 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 recipientContains "recipient" field
token::spend"spend"Spend a TokenContains "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 action
  • recipient - 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:

  1. With a TreasuryCap - token creator (or an application storing the TreasuryCap) can confirm any request by calling token::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 to mint and transfer tokens bypassing the restrictions.
  2. With a TokenPolicy - token creator can create a shared TokenPolicy 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.
  3. With a TokenPolicyCap - the capability managing the TokenPolicy can also be used to confirm requests. This can be useful for applications that have the TreasuryCap 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>);
info

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.

info

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

ActionRequests 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.

info

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>;