Sui - Transfer Policy

Sui - Transfer Policy


4 min read

Hello Everyone..

Welcome to another day of exploring Web3 Engineering. In today's article, let us look into the transfer_policy provided in the sui framework. So, without any further ado, let us get started.

Transfer Policy

As the name suggests, transfer policy is used to impose some rules and procedures while transferring objects in the sui blockchain. And also, if we want to list our assets in the sui kiosk system, our asset must have a transfer policy.

This transfer policies can be applied to the assets by using the transfer_policy module provided by the sui framework as shown in the following code.

fun init(witness: NFT, ctx: &mut TxContext) {
    let publisher = sui::package::claim(witness, ctx);
    let (mut policy, policyCap) = sui::transfer_policy::new<Token>(&publisher, ctx);
    transfer::public_transfer(policyCap, tx_context::sender(ctx));
    transfer::public_transfer(publisher, tx_context::sender(ctx));

The transfer_policy::new is used to create a new policy and it requires 2 parameters and 1 type argument:

  1. Reference to the publisher object of the package.

  2. Mutable reference of the transaction context

  3. Type of the asset to apply the policy.

The new function will return the TransferPolicy object and TransferPolicyCap objects. Usually the policy is shared and the policy cap is transferred to the package publisher. If the user wants to make any changes to the policy, the publisher have to use TransferPolicyCap for authentication. By default the new function will create a empty transfer policy with no rules.

How does the Transfer Policy Work ?

  1. Transfer policy acts as a ticketing system and allocates a TransferRequest<T> which then further is used to follow all the rules applied in the policy.

  2. For completing a rule specified in the policy, it will attach a Receipt of the Rule.

  3. Once all the receipts are received then we need to call the confirm_request function on the module.

  4. This function will confirm whether the request has completed all the rules and have receipts attached within or not and will then release our object.

Full code to integrate

/// Module: nft
module nft_transfer_policy::nft {
    use std::ascii::String;

    use sui::tx_context::sender;
    use nft_transfer_policy::royalty;

    public struct NFT has drop {}

    public struct Token has key, store {
        id: UID,
        name: String,

    fun init(witness: NFT, ctx: &mut TxContext) {

        let publisher = sui::package::claim(witness, ctx);
        let (mut policy, policyCap) = sui::transfer_policy::new<Token>(&publisher,ctx);

        royalty::add(&mut policy, &policyCap, 100u64);
        transfer::public_transfer(policyCap, tx_context::sender(ctx));
        transfer::public_transfer(publisher, tx_context::sender(ctx));

    public entry fun mint(name: String,  ctx: &mut TxContext) {
        transfer::public_transfer(Token {
            id: object::new(ctx),
        }, sender(ctx));

In the above example, we have created a new transfer policy and attached a royalty rule to it Now let us look into writing the rules.

/// Module: royalty
module nft_transfer_policy::royalty {
    use sui::sui::SUI;
    use sui::coin::{Self, Coin};
    use sui::transfer_policy::{Self as policy, TransferPolicyCap, TransferPolicy, TransferRequest};

    const MAX_BP: u64 = 10_000;
    const EInsufficientAmount: u64 = 0;

    public struct RoyaltyRule has drop {}

    public struct RoyaltyConfig has store, drop {
        royalty: u64

    public fun add<T>(
        policy: &mut TransferPolicy<T>,
        cap: &TransferPolicyCap<T>,
        royalty: u64
    ) {
        policy::add_rule(RoyaltyRule {}, policy, cap, RoyaltyConfig {royalty})

    public fun pay<T>(
        policy: &mut TransferPolicy<T>,
        request: &mut TransferRequest<T>,
        payment: &mut Coin<SUI>,
        ctx: &mut TxContext
    ) {
        let paid = policy::paid(request);

        let config: &RoyaltyConfig = policy::get_rule(RoyaltyRule {}, policy);

        let amount = (paid * config.royalty) / MAX_BP;

        assert!(coin::value(payment) >= amount, EInsufficientAmount);

        let fee = coin::split(payment, amount, ctx);
        policy::add_to_balance(RoyaltyRule {}, policy, fee);
        policy::add_receipt(RoyaltyRule {}, request)

In the above code,

  • RoyaltyRule - Type of the rule. It should only have drop ability and nothing else.

  • RoyaltyConfig - The config parameters to be stored for the policy. In our case, we have the royalty amount.

  • add() - A utility function that is used to add the rule to the policy object.

  • pay() - Function to pay the royalty fee.

If we look at the pay function, it will first call the paid function to get the amount paid and then it will calculate the amount and add into the policy. Once that is done, it will add the receipt to the request.

Once, all the receipts are added to the request, then we can call 0x2:sui:transfer_policy:confirm_request to complete our request.