llms.txt
Skip to main content

Receiving as Object

The address owned object state supports two types of owners: an account and another object. If an object was transferred to another object, Sui provides a way to receive this object through its owner's UID.

This feature is also known as "Transfer to Object" or TTO.

Definition

Receiving functionality is implemented in the sui::transfer module. It consists of a special type Receiving which is instantiated through a special transaction argument, and the receive function which takes a UID of the parent.

The T in transfer::receive is subject to the Internal Constraint. The public version of receive is called public_receive, and like other storage functions it requires T to have store.

module sui::transfer;

// An ephemeral wrapper around `Receiving` argument. Provided as a special input
// in a Transaction Block.
// Note: this type should be explicitly imported to be used!
public struct Receiving<phantom T: key> has drop {
id: ID,
version: u64,
}

/// Receive `T` from parent `UID` through special type `Receiving`.
public fun receive<T: key>(parent: &mut UID, to_receive: Receiving<T>): T;

Because receive requires a mutable reference to the parent's UID, receiving is only possible through the module that defines the parent - or through the access it chooses to expose. An object whose module provides no receiving implementation cannot release the objects sent to it, so this feature should be used with caution and in a controlled setting.

Example

As an illustration of transfer and receive, consider a PostOffice that registers post boxes and lets anyone send objects to them:

module book::receiving;

use sui::derived_object;
use sui::transfer::Receiving; // not imported by default!

/// Base derivation object to create derived `PostBox`-es.
public struct PostOffice has key { id: UID }

/// Object with derived UID which receives objects sent to an address.
public struct PostBox has key { id: UID, owner: address }

/// Transfer functionality. Anyone can come to the PostOffice and send to a specific
/// recipient's PostBox. Items can be received from the `PostBox` by the recipient.
public fun send<T: key + store>(office: &PostOffice, parcel: T, recipient: address) {
let postbox = derived_object::derive_address(office.id.to_inner(), recipient);
transfer::public_transfer(parcel, postbox)
}

/// Receive the parcel. Requires the sender to be the owner of the `PostBox`!
public fun receive<T: key + store>(
box: &mut PostBox,
to_receive: Receiving<T>,
ctx: &TxContext
): T {
assert!(box.owner == ctx.sender());

// Receive `to_receive` from `PostBox`.
let parcel = transfer::public_receive(&mut box.id, to_receive);
parcel
}

/// If user hasn't claimed their `PostBox` yet, create it.
/// Note: this is not a requirement for transferring assets!
/// Parcels can be sent even to unregistered post boxes, see `send` implementation.
public fun register_address(office: &mut PostOffice, ctx: &mut TxContext) {
transfer::share_object(PostBox {
id: derived_object::claim(&mut office.id, ctx.sender()),
owner: ctx.sender()
})
}

// Create a PostOffice on module publish.
fun init(ctx: &mut TxContext) {
transfer::share_object(PostOffice { id: object::new(ctx) });
}

Use Cases

Transferring to objects is a powerful feature that lets objects act as owners of other objects, and it enables designs that plain address ownership cannot express:

  • Controlled receiving. Because receiving goes through the parent's module, extra logic can be attached to it - the PostOffice above could, for example, charge a fee for every received item.
  • Objects as containers. A parent object collects assets sent to it and can itself be transferred, carrying its entire "inventory" along - without ever listing the contents in a transaction.
  • Deferred delivery. Assets can be sent to an object before its owner is ready to claim them - a post-box that accumulates items until the user activates their account.
  • Account-like objects. An object with an ID that receives and releases assets behaves much like an account, which makes TTO a building block for account-abstraction designs.

Sending to an object is also naturally parallel: transfers to an object ID are plain transfers - they do not reference the parent in the transaction, and therefore do not contend on it.

Next Steps

This section concludes the Using Objects chapter: you can now define objects, place them into any ownership state, manage their identity, and even make objects own other objects. The Advanced Programmability chapter builds on all of it - starting with the execution environment, and returning to object composition with Dynamic Fields, the second mechanism behind parent-child object relationships.

Further Reading

llms.txt