- The owner’s wallet. Only a wallet that holds the item now can ask for it back or cancel that.
- The vault request’s id. It names the vaulted item. Neither call takes a tap: the vault holds the item, so its owner cannot tap it.
not_found.resource.
Keep the vault request id
vault() resolves with the vault request.
Save its id with the item on your server when the owner vaults, and hand it
to the page that offers the unvault.
canVault names the request only until the item is
vaulted, and the owner cannot tap a vaulted item, because the vault holds it,
so the page has no later call that returns the id.
Your server can still find it. vaultRequests.list() in @endstate-sdk/core
returns a unit’s vault requests. At most one request of a unit is open at a
time: it reads pending, submitted, active, unvault_waiting or
unvaulting. From active on, the item is in the vault, so that request is
the vaulted item’s.
The flow
- Ask for the item.
requestUnvault({ requestId })sends the owner’s request from their wallet and resolves once the network included it. A few seconds later Endstate records it: the vault request readsunvault_waiting, withavailable_atset to the end of the waiting period. - Wait. The brand sets the waiting period for each vault it works with:
never less than that vault’s own minimum, and up to 30 days. The vault uses
it to prepare the item, and the item cannot change hands while it runs. The vault or the brand can place one
hold with a reason. A hold stops the clock, and the time it lasted is added
to
available_atwhen it is lifted. - Cancel, if the owner changes their mind. While the unvault waits,
cancelUnvault({ requestId })cancels it from the wallet. The item stays vaulted, the vault request readsactiveagain, and the owner can ask again later. - The vault dispatches the item. Once the waiting period has ended and
nothing holds the unvault, the item is ready to ship. The vault records the
dispatch with a tap of the item, Endstate completes the unvault, and the
vault request reads
unvaulted. The vault and the owner arrange delivery between them.
submission.not_executable, because Endstate records the request a few
seconds after the network includes it, so show the refusal and let the owner
try again after a moment.
Render one UnvaultPrompt per vaulted item, with key={requestId}. Each hook
holds the outcome of its latest call only, and data.resourceId names the
vault request that outcome is for.
When cancel is refused
The owner can cancel from the moment they ask until the item is ready to ship, including while a hold is in place. After that the vault may already be packing the item, socancelUnvault() rejects with
submission.not_executable:
- Ready to ship, not dispatched.
error.details.cancellable_atis the time a cancel opens again: 7 days after the waiting period ended. If the vault has still not dispatched the item by then, the owner can cancel. - Dispatched, or already unvaulted. The error carries no
cancellable_at. The unvault can no longer be cancelled.
error.details are the API’s own, so they stay in snake_case.
What the page can show
The page learns what its own calls report.requestUnvault() and
cancelUnvault() resolve with { transactionHash, address, resourceId }, and
a refusal says why the call could not go ahead.
The vault request carries the rest: where the unvault is, when the waiting
period ends, whether a hold stands and when the vault dispatched the item.
Your server reads it with
vaultRequests.get() in
@endstate-sdk/core and hands the page what it shows.
notes, the vault
staff’s free text, and a hold’s reason; pass them on only if the owner
should read them.
The SDK returns the fields below in camelCase: availableAt, readyToShip,
dispatchedAt.
string
active: the item is vaulted and no unvault is under way. unvault_waiting:
the owner asked for the item, and it is waiting or ready to ship.
unvaulting: the vault dispatched the item and Endstate is completing the
unvault. unvaulted: the unvault is complete.string | null
When the waiting period ends. Null until Endstate records the owner’s request,
and again after a cancel. Lifting a hold moves it later by the time the hold
lasted.
object | null
Set while a hold stands, else null.
by is vault or brand, whoever placed
it, and reason is their text.boolean
True once the waiting period has ended and no hold stands. The vault can
dispatch the item from then on. A cancel is refused from then until 7 days
after
available_at, when it opens again if the vault has still not
dispatched the item.string | null
When the vault recorded the item’s dispatch with its tap.
Follow an unvault
Webhooks announce each step, so your server does not have to poll. Subscribe an endpoint tovault_request.unvault_requested, vault_request.unvault_held,
vault_request.unvault_hold_lifted, vault_request.unvault_dispatched,
vault_request.unvaulted and vault_request.unvault_cancelled, and Endstate
sends it the request, as this read returns it, when the owner’s request or
cancel is recorded, when a hold is placed or lifted, when the vault dispatches
and when the unvault completes. See
Webhooks. No event is sent when the
waiting period ends: the item is ready to ship at available_at if no hold
stands. ready_to_ship in an event is its value when Endstate recorded the
change, so compute it from available_at and hold.
Reading the request stays available, for a page opened before an event
arrived or a server with no endpoint. Read it when the owner opens the page,
and while they watch it poll no more often than the page needs.
Endstate records the owner’s request a few seconds after requestUnvault()
resolves, so a read made straight away can still say active. Keep polling
until it reads unvault_waiting.
Stop polling when the request reads unvaulted: the unvault is complete and
the request is closed. A request that reads active after it read
unvault_waiting has no unvault under way, usually because the owner
cancelled it, so there is nothing to follow until they ask again.
Treat each read as the request’s current state, not as a one-way log.
Rarely, the network revises its latest records, and a status then moves back:
unvaulted to unvaulting or active, or unvault_waiting or unvaulting
to active. Read the request again when the owner next opens the page
instead of keeping unvaulted as final.
Each such change sends a webhook event with a higher revision, and a type you
already handled can arrive again, so act once per request, not once per
event. A request that reads active again after the vault dispatched the
item corrects the record; it does not mean the item is back in the vault. A
hold does not survive the change and is placed again if the unvault is
requested again. Corrections lists each
case.
This read is rate limited. Each response carries the X-RateLimit-Limit,
X-RateLimit-Remaining and X-RateLimit-Reset headers. A refused read
answers 429 rate_limit.exceeded with Retry-After: wait at least that long
before the next read, and back off if it repeats. Limits are set per
organization, as
Errors describes. Poll from
your server and share one answer between page views of the same item, not one
read per view.
Outcomes and the wallet
Each hook returns{ status, data, error, reset }, with status
"idle" | "pending" | "success" | "error". It holds the outcome of its latest
call, and data.resourceId names the vault request that outcome is for. A
page that shows several vaulted items renders one component per item, with
key={requestId}, so each item has its own outcome.
An outcome belongs to the wallet that sent it and to the sign-in. Another
wallet, a sign-out, a sign-in or reset() returns the hook to "idle", and a
call still in flight for the old one never lands. A new tap does not reset
these two hooks, unlike the vault hooks:
an unvault takes no tap, so a tap of another item leaves its outcome in place.
Both sends go from the owner’s wallet with the cost covered by Endstate; the
owner never pays for them. A call resolves once the network included the
send. Endstate records it a few seconds later, so a cancelUnvault() sent
straight after requestUnvault() resolves can reject with
submission.not_executable. Retry it after a moment.
If the item changes hands while it is vaulted, the new owner’s wallet sends
these calls and the previous owner’s is refused with auth.forbidden.
Errors
409
requestUnvault() found an unvault already under way or finished, or
cancelUnvault() found nothing it can cancel: no unvault is waiting, or the
item is ready to ship. error.details.cancellable_at, the time a cancel opens
again, is present only while the item is ready to ship and not dispatched.
Nothing was sent.409
An earlier unvault of the item is still settling, so a new one cannot start
yet. Retry after
error.details.retry_at.404
The id names no vaulted item in this environment: it is unknown, or its
request never reached the vault because it is still pending or was cancelled,
rejected or expired.
403
This wallet does not hold the item now.
The send cannot be covered right now. Nothing was sent, and the owner is never
asked to pay. Retry later.
404
Vaulting is not enabled for this environment.
EndstateWalletError for
the wallet’s own.
See also
useRequestUnvault
The hook that sends the owner’s request to unvault.
Vault an item
How the item got into the vault, and where the request id comes from.
vaultRequests.get
The server-side read that carries the unvault’s state.

