suideveloper
All questions
Objects

What is transfer to object (receiving) on Sui?

November 9, 2026

Transfer to object lets you send an object to another object's ID as if it were an address. That works because object IDs and account addresses are both 32 bytes and never overlap. The sent object becomes owned by the parent object. To use it later, a transaction passes it as a Receiving<T> argument, and Move code that has &mut UID access to the parent calls transfer::receive or transfer::public_receive to claim it. Because only the parent's module can hand out &mut UID, that module decides who can receive.

Sending

Nothing special is needed. You use the normal transfer functions with an object ID as the recipient:

Move
transfer::public_transfer(coin, @0xADD);          // to an account address
transfer::public_transfer(coin, object::id_address(&account)); // to an object

From a PTB or the CLI, transfer an object to the parent's ID the same way you would to an address. Check that the parent exists and isn't immutable: anything sent to an immutable object can never be received.

Receiving

sui::transfer defines the receiving interface:

Move
public struct Receiving<phantom T: key> has drop { /* ... */ }

public fun receive<T: key>(parent: &mut UID, to_receive: Receiving<T>): T;
public fun public_receive<T: key + store>(parent: &mut UID, to_receive: Receiving<T>): T;

receive only works inside the module that defines T, just like transfer::transfer. public_receive works anywhere for types with store. A Receiving<T> has only drop, so receiving is optional: unused tickets are discarded and the object stays where it is.

Example: an account with a stable deposit address

Move
module example::account;

use sui::coin::Coin;
use sui::dynamic_field as df;
use sui::transfer::Receiving;

const ENotOwner: u64 = 0;

public struct Account has key {
    id: UID,
    owner: address,
}

public struct BalanceKey<phantom T> has copy, drop, store {}

public fun create(ctx: &mut TxContext) {
    transfer::share_object(Account { id: object::new(ctx), owner: ctx.sender() });
}

/// Pull a coin that was sent to this account's ID into its balance.
public fun accept<T>(acct: &mut Account, sent: Receiving<Coin<T>>, ctx: &TxContext) {
    assert!(ctx.sender() == acct.owner, ENotOwner);
    let coin = transfer::public_receive(&mut acct.id, sent);
    let key = BalanceKey<T> {};
    if (df::exists(&acct.id, key)) {
        let bal: &mut Coin<T> = df::borrow_mut(&mut acct.id, key);
        bal.join(coin);
    } else {
        df::add(&mut acct.id, key, coin);
    }
}

Anyone can send coins to the Account's ID. This account is shared, so the module checks the sender before it lets anyone receive. Without that check, any caller could take the coins. The account's ID stays the same whether it is transferred, wrapped or moved into a dynamic field, so it works as a stable deposit address.

Calling it from TypeScript

With @mysten/sui v2 you pass the sent object's ID with tx.object. The SDK sees that the function expects Receiving<...> and builds a receiving input:

TypeScript
import { Transaction } from '@mysten/sui/transactions';

const tx = new Transaction();
tx.moveCall({
  target: `${PACKAGE_ID}::account::accept`,
  typeArguments: ['0x2::sui::SUI'],
  arguments: [tx.object(ACCOUNT_ID), tx.object(SENT_COIN_ID)],
});
await client.signAndExecuteTransaction({ signer, transaction: tx });

If you already have the full reference, tx.receivingRef({ objectId, version, digest }) builds the input explicitly.

Finding objects sent to an object

The owned-objects query works the same way whether the owner is an account or an object. In GraphQL, say which one you mean:

GraphQL
query {
  objects(filter: { owner: "0x<ACCOUNT_ID>", ownerKind: OBJECT }) {
    nodes { address asMoveObject { contents { type { repr } } } }
  }
}

In TypeScript, client.listOwnedObjects({ owner: ACCOUNT_ID }) also returns them.

Rules worth knowing

Child abilities Parent module can restrict Child module can restrict
key Yes Yes
key + store Yes No
  • With key only, the child's module must provide its own receive function. Otherwise the object is stuck once sent. Combining custom transfer and receive rules is how the docs build soul-bound objects.
  • Transfer to object doesn't work for party objects whose owner is an object ID.
  • The parent must be passed mutably (&mut), so receiving through a shared parent is sequenced through consensus. See When should I use shared objects versus owned objects?.

Sources

More on objects