Skip to content

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() exposes orderIdToViewingAuth[orderId] and the order object may have order.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:

  1. Eligibility: The order is COMPLETED on-chain and a cooldown (e.g. 20 minutes after deposit) has passed. Use checkRecovery(orderId, depositTimestamp) from useRefund; it sets isRecoveryEligible.
  2. 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 exportSecretMnemonicFromEntropy from 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’s checkRecovery and isRecoveryEligible.
  • Uses useWallet and useAuth to 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

NeedHook / flowNotes
Track an order (live status)useOrderTracking({ client, orderId, viewingAuth, onComplete })Needs orderId + viewingAuth (from context or URL).
Refund expired deposituseRefundcheckRefund then refundWallet must be refundee; switch to Avalanche first.
Recovery (mnemonic export)App-level: useOrderRecovery + exportSecretMnemonicFromEntropyAfter 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.