Skip to main content
A transfer moves an item between existing owners. The item already has an owner, and a recent tap of its chip - proof someone is holding it - authorizes handing it to someone new. You supply the recipient’s wallet address, and the session token the tap opened authorizes the move. That is what sets a transfer apart from a claim, which is the first assignment of ownership out of a tap. A claim has no prior owner, so Endstate can submit it for you. A transfer has one, so only that current owner can complete it: Endstate authorizes the move and hands back a transaction, but never moves an item on an owner’s behalf.

The flow

React is the primary path. useTransfer() gives you a transfer({ to }) call bound to the verified session the landing tap opened, and useSession() holds the item being moved. The browser quickstart covers how the tap is captured and verified, and Tap sessions covers what the session is. Prefer the hooks; the vanilla session.transfers.create is the same call without React.
transfer({ to }) resolves as soon as Endstate prepares and authorizes the transaction, returning the Transfer with its transaction attached. The hook exposes that result as data, any failure as error, and where the operation stands as status - a TransferStatus of idle, preparing, settling, confirmed, expired, failed, or error. It starts idle, moves to preparing while the request is in flight, and lands on error if the request throws. Because wait defaults to false, the hook stops there: it does not poll settlement, since a transfer cannot settle until the current owner broadcasts the returned transaction, which the hook never does. Read the returned transfer’s own status (prepared) and observe settlement out of band. Passing wait: true continues through settling to confirmed, expired, or failed, but only after the owner has broadcast - so most transfers leave it false. A transfer takes one field:
string
required
The recipient’s EVM wallet address (0x followed by 40 hex characters) - the account that will receive the item. If the recipient is getting an Endstate wallet on this page, this is the address from the wallet ((await wallet.ready()).address, or useWallet().account?.address under the provider).
The transfer reads unit_id from the session’s scope, so you never pass it. A session freshly opened by verify() - including the landing tap autoVerify captures - already carries that scope; one you adopted from a bare token does not, and the call throws EndstateSessionScopeError until the session’s scope is refreshed.

Choosing who submits

A transfer is always put through by the current owner - Endstate authorizes the move but never submits it for you, and unlike a claim there is no Endstate-submitted mode and no execution choice. The move is rejected unless the owner’s own account sends it. What is changing is the signer the owner reaches for.

Endstate wallet (coming soon)

An owner who holds an Endstate wallet will broadcast the transfer straight from it, with no separate wallet library. The Endstate web wallet provisions an address today but exposes no signing surface; signing operations arrive in a later minor release. Until then, the owner broadcasts with their own signer, below.

The current owner broadcasts

transfer (and vanilla session.transfers.create) returns a transaction object, { to, data }, that Endstate has already authorized with its signature and leaves for the current owner to send. The owner is the from address on the returned transfer, and the move is rejected unless that same account sends it - no relayer or other funded account can stand in. Endstate never submits a transfer, so the current owner broadcasts the returned transaction with their own signer, then reads settlement from the transfer record. The exact transaction shape and how to broadcast it live in session.transfers.create. Broadcast well within about 30 minutes of preparing the transfer: the signed authorization expires after that and the transfer settles expired.

Settlement

A transfer is created as prepared and lands on one of three terminal statuses:
  • prepared - authorized and waiting for the current owner to broadcast it.
  • confirmed - done. Ownership has moved and settled.
  • expired - the signed authorization’s window (about 30 minutes) passed before the owner broadcast it. Create a new transfer.
  • failed - the transaction did not settle. Create a new transfer.
In React, read this from useTransfer().data.status, or opt into hook-driven waiting with transfer({ to, wait: true }) once the owner has broadcast. With the vanilla client, session.transfers.waitUntilSettled polls until the status is terminal so you do not write the loop:
It reads the transfer on a backoff - starting at 1s, doubling to a 5s ceiling, and giving up at 120s by default - all overridable through its options. To poll yourself instead, call session.transfers.get(transferId), or endstate.transfers.get(unitId, transferId) from a secret-key client on your backend. On expired or failed, create a new transfer.

Idempotency

Creating a transfer is a mutation, so it is safe to retry with an idempotency key. Pass idempotencyKey; omit it and core generates one for you. Replaying the same key with the same body returns the original transfer rather than opening a second one; the same key with a different body is rejected as a conflict.
With the vanilla client the key rides in the second options argument: session.transfers.create({ to }, { idempotencyKey }).

Errors

Every failure throws a typed error carrying a branchable code; in React it surfaces as useTransfer().error. For how those errors behave and what retries automatically, see Errors and retries; for the full transfer code list - unit.not_minted, transfer.owner_unknown, transfer.in_progress, transfer.already_to_recipient, session_token.wrong_chip, and the rest - see session.transfers.create.

Next steps

session.transfers.create

The transfer request body, response, transaction shape, and every error code.

Claim ownership

Assign first ownership out of a tap - the sibling action to a transfer.

Wallet

Provision the recipient address a transfer sends the item to.