Skip to main content

Custom Transfer Rules

Every IOTA object must have the key ability. The store ability, on the other hand, is optional and provides more flexibility for how objects are managed and transferred. Objects with the store ability:

However, when creating custom transfer rules, it's crucial to note that if an IOTA object O does not have the store ability, the iota::transfer::public_transfer function cannot be used to transfer it. Instead, the Move module defining O exclusively controls how such objects are transferred, typically using the iota::transfer::transfer function. This allows the module to implement custom transfer functions for O, which can enforce specific conditions (such as requiring a fee) before a transfer is allowed.

The Store Ability and Transfer Rules

Custom transfer rules provide a mechanism to define the conditions under which an object can be transferred. If you add the store ability to an object, you are effectively enabling unrestricted transfers of that object without the involvement of its defining module. Once public transfers are allowed, custom transfer rules cannot be reinstated or enforced on that object.

Example

Consider an example where you want to create an object type O that can only be transferred if a specific unlocked flag within it is set to true:

public struct O has key {
id: UID,
// An `O` object can only be transferred if this field is `true`
unlocked: bool
}

In the same module that defines the object O, you can create a custom transfer function transfer_unlocked, which checks the unlocked flag before allowing the transfer:

module examples::custom_transfer {
// Error code for trying to transfer a locked object
const EObjectLocked: u64 = 0;

public struct O has key {
id: UID,
// An `O` object can only be transferred if this field is `true`
unlocked: bool
}

// Check that `O` is unlocked before transferring it
public fun transfer_unlocked(object: O, to: address) {
assert!(object.unlocked, EObjectLocked);
iota::transfer::transfer(object, to)
}
}

With this setup, you can enforce that an object of type O is only transferable when it's unlocked. Additionally, you can define multiple transfer rules for the same object, each enforcing different conditions. For instance, you could create a rule that only allows locked objects to be transferred to a specific address:

const EObjectNotLocked: u64 = 1;
const HomeAddress = @0xCAFE;


public fun transfer_locked(object: O) {
assert!(!object.unlocked, EObjectNotLocked);
iota::transfer::transfer(object, HomeAddress)
}

Now, the module provides two custom transfer rules for any object O:

  • One that allows transfer if the object is unlocked
  • One that allows transfer of locked objects only to the address 0xCAFE.

Because O lacks the store ability, these custom rules are the only ways to transfer O—the iota::transfer::public_transfer function cannot be used.

By defining custom transfer rules, you maintain precise control over how and when your objects can be transferred, ensuring that your specific business logic is enforced during these operations.

Question 1/3

Which function is used to transfer an IOTA object when implementing custom transfer rules?