Skip to main content
Available from @rhinestone/deposit-modal v0.15.0, history is the self-service recovery path for both host modals. It lists deposits for the account in context and can recover an eligible failure when you provide signRecovery.
History lists deposits for the configured recipient.

Where history appears

The history entry point is available before a transaction and on terminal success or failure screens. It is hidden while the modal is signing, submitting, polling, or showing an external provider. Opening history during a flow preserves the underlying form or step, so Back returns the user to it. The entry-point badge summarizes the visible account: From v0.17.0, a pending or processing row also carries an arrival estimate: the expected duration, or delay framing once the wait runs long. It is computed from the row’s creation time, so it is the same after a reload, on another device, and for a deposit started outside the modal. A row with no served estimate renders as before. Without signRecovery, history remains available but read-only. The transaction-hash fallback is hidden and the badge does not use needs_you because the integration cannot offer recovery. Your proxy must forward GET /deposits for lookup and POST /deposits/recover for signed recovery. These are existing routes; history adds no backend handler or header.

Recover from a history row

An eligible failed row opens a review, signing, and outcome flow. The refund destination starts as the deposit recipient, remains editable, and is covered by the signature. Recovery is offered only when all of these conditions hold:
  • The raw deposit status is failed or rejected.
  • Both source and destination are EVM chains.
  • The deposit names a valid recipient matching the account whose history is open.
  • Your integration supplies signRecovery for that recipient.
Completed, pending, processing, non-EVM, recipient-less, and otherwise ineligible rows remain visible without a recovery action.

Recover by transaction hash

When signing is enabled, history also offers transaction-hash lookup for a recoverable deposit omitted from the list, including a spam-filtered deposit. The lookup is scoped to the account whose history is open—even in the withdraw modal. A hash that belongs only to another account returns no match. Recovering an unlisted deposit does not add it to the history list afterward. The package offers no cross-account lookup. A support case that must find another account’s deposit goes through the dashboard workflow.

Signing contract

signRecovery is optional on DepositModal and WithdrawModal and has the exported SignRecovery type:
getRecoveryWallet represents your app’s account lookup. Select the wallet by both signer and chainId, then use the signing API expected by its SDK. The callback receives the complete typed data, so do not reconstruct it. chainId is not the source chain from which funds are refunded. Use it to select the verifier context for a smart account. This matters for ERC-1271 and ERC-6492 accounts whose deployment or owners differ by chain; EOA signatures are unaffected. Return whatever the recipient verifies: A raw smart-account owner signature is usually insufficient. Sign through the account SDK so it produces the wallet’s expected ERC-1271 or ERC-6492 format. An ERC-6492 wrapper carries the account’s factory and factory data, so an account that does not exist on chain yet can still be verified — which matters, since a deposit can fail before the user’s account is ever deployed.
ox is already a dependency of viem, so this adds nothing to your install.

What the user signs

Two fields, both meaningful to the person signing: which deposit, and where the money goes. The destination is inside the signature, which is the property worth understanding. The service cannot pay anywhere other than the address the user signed for, so a compromised page cannot redirect the funds — and your success screen can state where they went without trusting a response to echo it back.
A signature stays valid until the deposit is claimed — there is no expiry. Replay is already closed, because claiming moves the deposit out of failed/rejected and a second attempt is rejected. But do not persist a signature: treat it as single-use and discard it once the request returns.

Handling failures

A failed recovery response carries a machine-readable code, exported as RecoveryErrorCode. Switch on that rather than the HTTP status — the code is the contract, and the correct advice differs between codes that share a status. History recovery maps these to copy and to whether it offers a retry, so you get this for free. Handle them yourself only if you drive your own UI.
Never retry REFUND_RECONCILIATION_REQUIRED. The funds may already be moving, and the deposit needs an operator either way — contact support with the deposit.

When the recipient can’t sign

Some recipients have no key to sign with: a wallet the user has lost, or a recipient your app controls rather than the user. For those, @rhinestone/deposit-modal/server exports createRefundHandler, which authorizes on your say-so instead of a signature — you decide who the caller is, and it verifies the deposit belongs to them before spending your API key.
authorize returns the deposit recipient the caller owns, or null to reject with 401. Add refundDestination to pin where the money goes rather than letting the request choose. depositRecipient never decides the destination — the handler lists deposits settling to it and rejects a txHash that isn’t among them. It returns a (Request) => Promise<Response>, so it mounts in any fetch-based runtime: Next.js route handlers, Hono, Bun.serve, Cloudflare Workers.
Import from @rhinestone/deposit-modal/server only — it holds your API key and must never reach the browser. Neither modal calls this route; drive your own UI against it.
Never refund to an exchange deposit address. Exchanges don’t credit arbitrary incoming transfers, so the funds may be lost. This is why recovery never defaults the destination to the deposit’s sender.

Host callbacks and analytics

History navigation emits the deposit or withdraw history events in the current host session. Starting recovery creates a correlated claim session, and its ClaimAnalyticsEvent values arrive through the host modal’s onEvent callback:
History does not add claim events to onLifecycle. enableAnalyticsIngest={false} disables Rhinestone collection but does not change what onEvent receives, and analytics delivery never blocks recovery.

Other recovery models

Choose the authorization model that matches the caller:
  • User self-service: use account-scoped history with signRecovery.
  • Recipient cannot sign: authorize an operator flow on your server with createRefundHandler. It is not called by either modal and must never be exposed as a browser proxy route.