useWallet reads that account and its provisioning status, and the EndstateProvider you already wrap your tree in takes the wallet config. The wallet provisions and reports the account and nothing more - it exposes no signing surface of its own, because a claim is signed by Endstate and a transfer is broadcast by the current owner’s own signer. The account address is the whole of what a customer’s claim needs: it is the recipient of a claim, the step the browser quickstart walks end to end. Capturing and verifying the tap that opens that claim is separate and carries none of the wallet’s requirements - see Tap capture.
How provisioning works
The wallet is not a library that runs in your page. It is a document Endstate hosts, loaded into a hidden frame, that your page talks to over a narrow, origin-checked message bridge:- The frame is on an Endstate origin. Once the wallet is enabled, the SDK appends a hidden iframe pointed at
https://wallet.endstate.io. Because the wallet’s own document owns the key material, that material is isolated from your page by the browser’s origin boundary. Your page can ask for an account and a session; it can never read a private key. - The origins are compiled in. The frame origins ship inside the published bundle and are not configurable. Every message the bridge sends is addressed to that one origin, and every message it accepts is checked against it. This is a security property of the design, not a default you can override.
- There is no signing surface. The account address is the whole of what the wallet exposes. Nothing in the API accepts bytes to sign or a transaction to authorize.
The wallet is document-scoped - it lives entirely client-side. In React, the
provider subtree that owns the wallet must be a client component (
"use client"); it never mounts on the server. In Vanilla, createWallet throws if
window or document is absent.Provisioning the wallet
Wrap your tree inEndstateProvider once and hand it the wallet config; useWallet then reads the account anywhere inside. The config is an EndstateWalletConfig: getIdentityToken supplies the identity credential each session is established from, and enabled gates provisioning on your sign-in state. There is no environment to set.
useWallet() returns { account, status, error, ready, refresh, expiresAt }:
account- theWalletAccount({ address }), ornulluntil provisioning finishes.status- aWalletStatus:"unconfigured","idle","provisioning","ready", or"error". It stays"unconfigured"while the wallet is not enabled, moves through"provisioning"as the frame mounts and the first session is established, and lands on"ready"once the account exists.error- the failure whenstatusis"error", otherwisenull.ready()- resolves with theWalletAccountonce it exists; await it where you first need the address.refresh()- establishes a fresh session and resolves with the account.expiresAt- aDatefor when the current session lapses, ornullbefore it is ready.
createWallet returns an EndstateWallet immediately, with the frame already loading behind it, and you await wallet.ready() where the address is needed.
() => string | Promise<string>
required
Supplies the identity credential each session is established from. Called
fresh every time, because the credential is single-use and short-lived, and
only once
enabled is true.boolean
Gates provisioning on your sign-in state. While it is false the wallet stays
unconfigured and getIdentityToken is never called; set it true once the
customer is signed in and the wallet provisions.HTMLElement
Where the hidden frame is appended. Defaults to
document.body.{ frameReadyMs?: number; sessionMs?: number }
Wait budgets in milliseconds. Defaults:
frameReadyMs 5000, sessionMs
30000.Gating on sign-in
getIdentityToken runs only once enabled is true. Point enabled at your sign-in state - with BetterAuth, enabled: session != null, or your own isSignedIn flag - and the wallet stays unconfigured for signed-out visitors, calls nothing, and provisions the moment they sign in. Because the callback fires only behind that gate, a signed-out user never triggers a mint against your identity endpoint, and the credential is requested exactly when a session is needed.
The identity credential
getIdentityToken is the one integration point you own. It returns a dedicated, single-use identity credential for the signed-in customer: a short-lived JWT, expiring in about a minute, that carries the customer’s identity and is neither a session token nor a token used for anything else. That short life is what makes handing it to a browser acceptable - it is useless anywhere else, and anything else is useless here.
Endstate expects a JWT signed by your registered key, carrying the customer’s identity. Its decoded payload:
TypeError. It runs under the session wait budget, so a slow backend times out as wallet.timeout rather than hanging.
Where the token comes from depends on how your customers sign in:
- Bring your own auth. Your backend mints and signs it from your own login. See Bring your own auth for the exact claim contract - the accepted algorithms, the 300-second age ceiling, and the JWKS Endstate verifies against - and how to register your issuer.
- Endstate-managed identity. Endstate issues the credential against the customer’s Endstate sign-in, so the page must be an Endstate-hosted surface. You still return it through the same callback.
Refreshing and expiry
A session is time-bounded. In React,useWallet().expiresAt tells you when it lapses and refresh() establishes a new one; in Vanilla the same pair is wallet.sessionExpiresAt() and wallet.refreshSession(). Either calls getIdentityToken again for a fresh single-use credential, so your callback must be ready to issue another. Check expiresAt to refresh ahead of expiry rather than waiting for a call to fail, and treat a refresh as the remedy for wallet.session_expired: when a call fails with that code, refresh and retry.
Error handling
Every wallet failure carries anEndstateWalletError with a stable code. Branch on code, never on message - messages are for humans and may change. In React the error surfaces on useWallet().error; in Vanilla the lifecycle methods reject with it. Either way it is the same object.
code is one of three kinds:
- A
wallet.*code from the frame, such aswallet.session_expired. - An API error code, forwarded verbatim from the Endstate API.
- A code this package produced locally:
wallet.timeout(no response within budget),wallet.destroyed(teardown settled the call), and - when the wallet’s reply could not be used -wallet.unsupported_protocol_versionorwallet.internal_error.
wallet.unsupported_protocol_version means the frame speaks a protocol this build does not; upgrade @endstate-sdk/web.
string
The stable, branchable identifier for the failure.
string
Human-readable detail. Do not branch on it.
string
Documentation for this code, when the failure carried one.
string
Present when the failure originated at the Endstate API. Quote it in support
requests.
Record<string, unknown>
Structured context for the failure, when the wallet supplied any.
Next steps
Claim ownership
Send the item to the account address this wallet provisions.
createWallet
The Vanilla signature and options for mounting a wallet frame.
EndstateWalletError
Every code the wallet can throw and what to do with it.
Bring your own auth
Mint the identity credential from your own login.

