Order tracking, refund & recovery
The React SDK provides hooks to track an order in real time, check and execute refunds for expired unclaimed deposits, and check recovery eligibility. The example app (silentswap-v2-app) uses these in the order detail view and on dedicated refund/recover pages.
:::info Save order ID and viewing auth To track, refund, or recover an order later (e.g. from another device or after closing the app), save the order ID and viewing auth when the order is created. See Save auth & order ID (refund & recovery) in the Core SDK docs. :::
Order tracking with useOrderTracking
useOrderTracking connects to the SilentSwap WebSocket and keeps order status in sync. Use it on an order detail page so users see deposit and output stages (e.g. BRIDGE_SENT → BRIDGE_CFRM → FINALIZED).
Basic usage
Pass client, orderId, and viewingAuth. The hook connects when both orderId and viewingAuth are set and disconnects when they are cleared.
import { useOrderTracking, useSilentSwap } from '@silentswap/react';
function OrderTracking({ orderId, viewingAuth }: { orderId: string; viewingAuth: string | undefined }) {
const { client } = useSilentSwap();
const {
error: orderTrackingError,
orderStatus,
deposit,
outputs,
statusTexts,
isComplete,
} = useOrderTracking({
client,
orderId,
viewingAuth,
onStatusUpdate: (status) => console.log('Status:', status),
onError: (err) => console.error(err),
onComplete: () => console.log('Order completed'),
});
if (orderTrackingError) return <div>Error: {orderTrackingError.message}</div>;
if (!orderStatus) return <div>Connecting...</div>;
return (
<div>
<p>Deposit: {deposit?.amount} (tx: {deposit?.tx})</p>
<ul>
{outputs.map((o, i) => (
<li key={i}>{o?.stage ?? 'NONE'} – {o?.recipient}</li>
))}
</ul>
{isComplete && <p>Complete</p>}
</div>
);
}Where viewingAuth comes from
- From context: If the order was created in this session,
useOrdersContext()exposesorderIdToViewingAuth[orderId]and the order object may haveorder.auth. - From URL: The app can pass
?orderId=...&auth=...so users can open an order with saved credentials.
Example (from the app’s OrderTracking.tsx):
const { orderIdToViewingAuth, orders } = useOrdersContext();
const order = orders.find((o) => o.orderId === orderId) || null;
const urlAuth = new URLSearchParams(window.location.search).get('auth');
const viewingAuth = order?.auth ?? orderIdToViewingAuth[orderId] ?? urlAuth ?? undefined;Return values
- orderStatus – Full order status (signer, deposit, outputs with stage/recipient/txs).
- deposit –
{ amount, timestamp, duration, orderId, tx }. - outputs – Array of output status (stage, timestamp, recipient, asset, value, txs).
- statusTexts – Human-readable status strings for UI.
- isComplete – True when all outputs have stage
FINALIZED. - error – Connection or protocol error.
Refund with useRefund
Refunds are for expired, unclaimed deposits (order stayed OPEN past expiration). Only the refundee (the signer that placed the order) can call the refund transaction on the Avalanche gateway.
useRefund API
import { useRefund } from '@silentswap/react';
import { createPublicClient, http } from 'viem';
import { avalanche } from 'viem/chains';
const publicClient = createPublicClient({
chain: avalanche,
transport: http(),
});
const {
checkRefund, // (orderId, s0xGatewayAddress) => Promise<RefundEligibility | null>
refund, // (orderId, s0xGatewayAddress) => Promise<Hex | null>
checkRecovery, // (orderId, depositTimestamp) => Promise<boolean>
refundEligibility,
isRecoveryEligible,
isLoading,
error,
} = useRefund({
orderId: orderId ?? undefined,
publicClient,
walletClient: walletClient ?? undefined,
s0xGatewayAddress: client.s0xGatewayAddress,
onSuccess: (txHash) => toast.success(`Refund tx: ${txHash}`),
onError: (e) => toast.error(e.message),
});When refund is available
- Contract status is OPEN and the deposit expiration time has passed.
- The connected wallet must be the refundee (same address that placed the order). If the user has the mnemonic, they can recover that wallet and then refund.
Example: Refund page
The example app’s Order Refund page (/refund-order) lets users enter (or receive via URL) orderId and viewing auth, connect the refundee wallet, and execute refund. Refund must be sent on Avalanche; the app uses ensureChain(NI_CHAIN_ID_AVALANCHE, walletClient, connector) before calling refund().
// Simplified from OrderRefundPage.tsx
const { refund, isLoading } = useRefund({
orderId: orderId || undefined,
publicClient,
walletClient: walletClient || undefined,
s0xGatewayAddress: client.s0xGatewayAddress,
onSuccess: (txHash) => showSuccess(`Refund submitted: ${txHash}`),
onError: (e) => showError(e.message),
});
async function handleRefund() {
if (!orderId || !walletClient || !connector) return;
await ensureChain(NI_CHAIN_ID_AVALANCHE, walletClient, connector);
await refund(orderId, client.s0xGatewayAddress);
}Optional: call checkRefund(orderId, client.s0xGatewayAddress) first and only enable the button when refundEligibility?.refundable === true.
Recovery
Recovery in the SDK means:
- Eligibility: The order is COMPLETED on-chain and a cooldown (e.g. 20 minutes after deposit) has passed. Use
checkRecovery(orderId, depositTimestamp)fromuseRefund; it setsisRecoveryEligible. - App-level recovery: In the example app, “Recovery” lets the user export the mnemonic for the facilitator group that was used for that order (via
exportSecretMnemonicFromEntropyfrom the Core SDK), so they can recover funds manually if delivery failed. This requires the same wallet and auth used when the order was created.
The recover-order page in the app redirects to refund-order with the same query params (orderId, auth). So “recover” in the UI is: open refund page with saved orderId + auth, connect the refundee wallet, and either refund (if the order is still OPEN and expired) or use the app’s Recovery flow (expose mnemonic + paths) when the order is completed and recovery is eligible.
Mnemonic extraction (Core SDK)
Recovery uses the mnemonic and derivation paths for the facilitator group that was used for the order:
- Mnemonic: Derived from the same entropy used when the facilitator group was created. Call
exportSecretMnemonicFromEntropy(entropy)from@silentswap/sdk(Core). The app gets entropy from the auth-derived wallet (wallet.entropy). - Recovery paths: BIP-44 paths for each (coin type, address index) from the group's public keys:
m/44'/${coinType}'/${accountIndex}'/0/${addressIndex}. Skip the generic coin type'*'. The account index is the group's order index (e.g. deposit count); address index is the output index (0 for single-output).
Full recovery description (entropy → mnemonic, path format, script usage) is in Save auth & order ID (refund & recovery).
useOrderRecovery (example app)
The example app defines a custom useOrderRecovery hook that:
- Uses
useRefund’scheckRecoveryandisRecoveryEligible. - Uses
useWalletanduseAuthto get the facilitator wallet for the current user. - Matches the order’s default public key to the wallet’s facilitator accounts, then builds derivation paths and calls
exportSecretMnemonicFromEntropy(wallet.entropy)to show the mnemonic and paths for manual recovery.
That hook is used in OrderTracking.tsx to show a “Recover” button when the order is completed and recovery is eligible; clicking it reveals the mnemonic (with a security warning).
Summary
| Need | Hook / flow | Notes |
|---|---|---|
| Track an order (live status) | useOrderTracking({ client, orderId, viewingAuth, onComplete }) | Needs orderId + viewingAuth (from context or URL). |
| Refund expired deposit | useRefund → checkRefund then refund | Wallet must be refundee; switch to Avalanche first. |
| Recovery (mnemonic export) | App-level: useOrderRecovery + exportSecretMnemonicFromEntropy | After order COMPLETED and 20 min; same wallet/auth. |
For Core SDK usage (no React), see Save auth & order ID (refund & recovery) and Tracking order via WS.