Transfer Module
The iota::transfer module provides functions for transferring object ownership between addresses and converting objects between different ownership models (owned, shared, frozen).
Source: crates/iota-framework/packages/iota-framework/sources/transfer.move
Core Concepts
Ownership Models
IOTA objects can have three ownership states:
- Owned: Owned by a specific address
- Shared: Accessible by anyone (mutable or immutable)
- Frozen: Immutable, cannot be modified or transferred
Receiving Type
public struct Receiving<phantom T: key> has drop {
id: ID,
version: u64,
}
Represents the ability to receive an object of type T. This is ephemeral and cannot be stored on-chain.
Transferring Objects
transfer
Transfer ownership of an object to a recipient address:
The object to transfer (must have key ability)
The address to transfer the object to
public fun transfer<T: key>(obj: T, recipient: address)
This function has custom bytecode verification that ensures T is defined in the calling module. Use public_transfer for objects with store.
Example:
module example::my_module {
use iota::transfer;
public struct MyObject has key {
id: UID,
value: u64,
}
public fun transfer_object(obj: MyObject, recipient: address) {
transfer::transfer(obj, recipient);
}
}
public_transfer
Transfer an object with the store ability to a recipient:
The object to transfer (must have key + store)
The address to transfer the object to
public fun public_transfer<T: key + store>(obj: T, recipient: address)
Use this for objects that have the store ability and can be transferred outside their defining module.
Example:
use iota::coin::Coin;
use iota::iota::IOTA;
// Coins have `store`, so use public_transfer
public fun send_coin(coin: Coin<IOTA>, recipient: address) {
transfer::public_transfer(coin, recipient);
}
Sharing Objects
share_object
Make an object mutable and shared - accessible by anyone:
The object to share (must have key)
public fun share_object<T: key>(obj: T)
Once shared, an object stays shared forever. The object must be newly created in the current transaction.
Example:
public struct SharedCounter has key {
id: UID,
count: u64,
}
public fun create_shared_counter(ctx: &mut TxContext) {
let counter = SharedCounter {
id: object::new(ctx),
count: 0,
};
transfer::share_object(counter);
}
public fun increment(counter: &mut SharedCounter) {
counter.count = counter.count + 1;
}
public_share_object
Share an object with the store ability:
The object to share (must have key + store)
public fun public_share_object<T: key + store>(obj: T)
Freezing Objects
freeze_object
Make an object immutable - it can no longer be transferred or mutated:
The object to freeze (must have key)
public fun freeze_object<T: key>(obj: T)
Example:
public struct ImmutableData has key {
id: UID,
data: vector<u8>,
}
public fun create_immutable_data(data: vector<u8>, ctx: &mut TxContext) {
let obj = ImmutableData {
id: object::new(ctx),
data,
};
transfer::freeze_object(obj);
}
public_freeze_object
Freeze an object with the store ability:
The object to freeze (must have key + store)
public fun public_freeze_object<T: key + store>(obj: T)
Receiving Objects
receive
Receive an object that was transferred to another object you own:
Mutable reference to the parent object’s UID
The receiving capability for the object
public fun receive<T: key>(parent: &mut UID, to_receive: Receiving<T>): T
Example:
public struct Parent has key {
id: UID,
}
public struct Child has key, store {
id: UID,
value: u64,
}
public fun receive_child(parent: &mut Parent, to_receive: Receiving<Child>): Child {
transfer::receive(&mut parent.id, to_receive)
}
public_receive
Receive an object with the store ability:
Mutable reference to the parent object’s UID
The receiving capability for the object
public fun public_receive<T: key + store>(parent: &mut UID, to_receive: Receiving<T>): T
receiving_object_id
Get the ID of the object referenced by a Receiving capability:
public fun receiving_object_id<T: key>(receiving: &Receiving<T>): ID
Complete Example
module example::game {
use iota::object::{Self, UID};
use iota::transfer;
use iota::tx_context::TxContext;
// Owned object
public struct GameCharacter has key {
id: UID,
name: vector<u8>,
level: u64,
}
// Shared object
public struct GameBoard has key {
id: UID,
players: u64,
}
// Immutable object
public struct GameRules has key {
id: UID,
rules: vector<u8>,
}
// Create an owned character
public fun create_character(name: vector<u8>, ctx: &mut TxContext) {
let character = GameCharacter {
id: object::new(ctx),
name,
level: 1,
};
transfer::transfer(character, ctx.sender());
}
// Transfer character to another player
public fun transfer_character(character: GameCharacter, new_owner: address) {
transfer::transfer(character, new_owner);
}
// Create a shared game board
public fun create_game_board(ctx: &mut TxContext) {
let board = GameBoard {
id: object::new(ctx),
players: 0,
};
transfer::share_object(board);
}
// Anyone can join the game
public fun join_game(board: &mut GameBoard) {
board.players = board.players + 1;
}
// Create immutable game rules
public fun create_rules(rules: vector<u8>, ctx: &mut TxContext) {
let rules_obj = GameRules {
id: object::new(ctx),
rules,
};
transfer::freeze_object(rules_obj);
}
}
Error Codes
const ESharedNonNewObject: u64 = 0; // Shared an object that was not newly created
const EBCSSerializationFailure: u64 = 1; // Serialization failed
const EReceivingObjectTypeMismatch: u64 = 2; // Wrong type for receiving
const EUnableToReceiveObject: u64 = 3; // Object doesn't exist or can't be accessed
const ESharedObjectOperationNotSupported: u64 = 4; // Operation not allowed on shared objects
const EAccountCannotReceiveObject: u64 = 5; // Accounts can't receive objects
Best Practices
-
Use
transfer for module-internal types: If the type is defined in your module and doesn’t have store, use transfer instead of public_transfer
-
Share objects carefully: Once an object is shared, it stays shared forever and anyone can access it
-
Freeze for immutability: Use
freeze_object when you want to create immutable reference data
-
Consider ownership models: Choose the right ownership model based on your use case:
- Owned: For user-specific assets (NFTs, account objects)
- Shared: For global state (registries, game boards)
- Frozen: For immutable data (rules, configurations)