Custom transfer

In Sui Move, objects defined with only key ability can not be transferred by default. To enable transfers, publisher has to create a custom transfer function. This function can include any arguments, for example a fee, that users have to pay to transfer.

module examples::restricted_transfer {
    use sui::balance::{Self, Balance};
    use sui::coin::Coin;
    use sui::sui::SUI;

    /// For when paid amount is not equal to the transfer price.
    const EWrongAmount: u64 = 0;

    /// A Capability that allows bearer to create new `TitleDeed`s.
    public struct GovernmentCapability has key { id: UID }

    /// An object that marks a property ownership. Can only be issued
    /// by an authority.
    public struct TitleDeed has key {
        id: UID,
        // ... some additional fields

    /// A centralized registry that approves property ownership
    /// transfers and collects fees.
    public struct LandRegistry has key {
        id: UID,
        balance: Balance<SUI>,
        fee: u64

    /// Create a `LandRegistry` on module init.
    fun init(ctx: &mut TxContext) {
        transfer::transfer(GovernmentCapability {
            id: object::new(ctx)
        }, ctx.sender());

        transfer::share_object(LandRegistry {
            id: object::new(ctx),
            balance: balance::zero<SUI>(),
            fee: 10000

    /// Create `TitleDeed` and transfer it to the property owner.
    /// Only owner of the `GovernmentCapability` can perform this action.
    public fun issue_title_deed(
        _: &GovernmentCapability,
        `for`: address,
        ctx: &mut TxContext
    ) {
        transfer::transfer(TitleDeed {
            id: object::new(ctx)
        }, `for`)

    /// A custom transfer function. Required due to `TitleDeed` not having
    /// a `store` ability. All transfers of `TitleDeed`s have to go through
    /// this function and pay a fee to the `LandRegistry`.
    public fun transfer_ownership(
        registry: &mut LandRegistry,
        paper: TitleDeed,
        fee: Coin<SUI>,
        to: address,
    ) {
        assert!(&fee.value() == registry.fee, EWrongAmount);

        // add a payment to the LandRegistry balance

        // finally call the transfer function
        transfer::transfer(paper, to)

Learn more