Skip to content

Example

End-to-end Exchange Pay deposit with React hooks: pick a provider, create an order, show checkout, wait for completion.

Requires SwappedConnectProvider, ExchangePayContextProvider, and a loaded active session. After a completed payment, call restartSession() before starting again.

Use state hooks for UI. Use event hooks only for side effects (navigate, toast).

1. Providers + session

tsx
import { createSwappedConnectClient } from '@swapped/connect-sdk'
import {
  ExchangePayContextProvider,
  SwappedConnectProvider,
} from '@swapped/connect-sdk/react'

const client = createSwappedConnectClient({
  sessionId: 'your-session-id', // from your backend
})

void client.loadSession()

function App() {
  return (
    <SwappedConnectProvider client={client}>
      <ExchangePayContextProvider>
        <ExchangePayFlow />
      </ExchangePayContextProvider>
    </SwappedConnectProvider>
  )
}

2. Pick an Exchange Pay provider

tsx
import { useExchangePayProviders } from '@swapped/connect-sdk/react'

function ProviderPicker() {
  const {
    paymentMethods,
    selectedProvider,
    selectProvider,
    isLoading,
  } = useExchangePayProviders()

  if (isLoading) return <p>Loading…</p>

  return (
    <ul>
      {paymentMethods.map(method => (
        <li key={method.id}>
          <button
            type="button"
            aria-pressed={selectedProvider === method.provider}
            onClick={() => selectProvider(method.provider)}
          >
            {method.name}
          </button>
        </li>
      ))}
    </ul>
  )
}

3. Deposit form — currency + amount + create order

tsx
import {
  useExchangePayAmount,
  useExchangePayCurrency,
  useExchangePayOrder,
  useExchangePaySelection,
  useExchangePaySupportedCurrencies,
} from '@swapped/connect-sdk/react'

function DepositForm({ onCreated }: { onCreated: () => void }) {
  const { currencies, isLoading, error: currenciesError } =
    useExchangePaySupportedCurrencies()
  const { selectedCurrency, selectCurrency, isCurrencySelected } =
    useExchangePayCurrency()
  const {
    amount,
    setAmount,
    setTouched,
    error: amountError,
    formattedMinAmount,
  } = useExchangePayAmount()
  const { canSubmit } = useExchangePaySelection()
  const { createOrder, isCreating, error: orderError } = useExchangePayOrder()

  async function onSubmit() {
    setTouched(true)
    if (!canSubmit) return

    await createOrder()
    onCreated() // navigate to checkout screen
  }

  if (isLoading) return <p>Loading currencies…</p>

  return (
    <div>
      {(currenciesError || amountError || orderError) && (
        <p>
          {(currenciesError?.message ??
            amountError?.message ??
            orderError?.message)}
        </p>
      )}

      <ul>
        {currencies.map(currency => (
          <li key={`${currency.symbol}:${currency.blockchain}`}>
            <button
              type="button"
              aria-pressed={isCurrencySelected(currency)}
              onClick={() => selectCurrency(currency)}
            >
              {currency.symbol} on {currency.blockchain}
            </button>
          </li>
        ))}
      </ul>

      <input
        value={amount}
        onChange={event => setAmount(event.target.value)}
        onBlur={() => setTouched(true)}
        inputMode="decimal"
        placeholder="0.00"
      />
      {selectedCurrency && (
        <p>Minimum ${formattedMinAmount}</p>
      )}

      <button
        type="button"
        disabled={isCreating || !canSubmit}
        onClick={() => void onSubmit()}
      >
        {isCreating ? 'Creating…' : 'Create order'}
      </button>
    </div>
  )
}
tsx
import {
  useExchangePayOrder,
  useExchangePayOrderCountdown,
  useOnExchangePayOrderCompleted,
  useOnExchangePayOrderExpired,
  useSwappedConnectClient,
} from '@swapped/connect-sdk/react'

function Checkout({ onExpired }: { onExpired: () => void }) {
  const client = useSwappedConnectClient()
  const { order, status, isCompleted, closeOrder } = useExchangePayOrder()
  const { hours, minutes, seconds } = useExchangePayOrderCountdown()

  // Side effects only — keep display data on state hooks
  useOnExchangePayOrderCompleted(() => {
    // e.g. navigate to success; summary is available via the summary hook
  })

  useOnExchangePayOrderExpired(() => {
    // e.g. toast.info('Order expired, please try again')
    onExpired() // navigate back to deposit form
  })

  if (!order) return <p>No active order</p>
  if (isCompleted) return <p>Payment received</p>

  const { qr, url, mobileUrl } = order.checkout

  return (
    <div>
      <p>
        Status: {status ?? 'PENDING'}
        {/* PENDING | PAY_SUCCESS | AWAITING_PROVIDER_FUNDS */}
      </p>
      <p>
        Time left: {hours}h {minutes}m {seconds}s
      </p>

      {qr?.type === 'image' && <img src={qr.value} alt="Pay QR" />}
      {qr?.type === 'url' && <p>Encode as QR: {qr.value}</p>}

      {url && (
        <a href={url} target="_blank" rel="noreferrer">
          Open checkout
        </a>
      )}
      {/* mobileUrl is for mobile devices only */}
      {mobileUrl && mobileUrl !== url && <a href={mobileUrl}>Open in app</a>}

      {/* If canClose is true, show Cancel — or call closeOrder() when the user goes back */}
      {order.canClose && (
        <button type="button" onClick={() => void closeOrder()}>
          Cancel order
        </button>
      )}

      {isCompleted && (
        <button type="button" onClick={() => void client.restartSession()}>
          Start another payment
        </button>
      )}
    </div>
  )
}

5. Success summary

tsx
import {
  useExchangePayCompletedTransactionSummary,
  useSwappedConnectClient,
} from '@swapped/connect-sdk/react'

function Success() {
  const client = useSwappedConnectClient()
  const summary = useExchangePayCompletedTransactionSummary()

  if (!summary) return null

  const network = summary.amount.network ?? summary.receive?.network

  return (
    <div>
      <p>Provider: {summary.provider}</p>
      <p>
        Paid {summary.amount.amount} {summary.amount.currency}
      </p>

      {/* Swap legs when the deposit involved a conversion */}
      {summary.isSwap && summary.send && (
        <p>
          Send {summary.send.amount} {summary.send.currency}
        </p>
      )}
      {summary.isSwap && summary.receive && (
        <p>
          Receive {summary.receive.amount} {summary.receive.currency}
        </p>
      )}

      {network && <p>Network: {network}</p>}
      {summary.fee && (
        <p>
          Fee {summary.fee.amount} {summary.fee.currency}
        </p>
      )}
      {summary.merchantFee && (
        <p>
          Merchant fee {summary.merchantFee.amount}{' '}
          {summary.merchantFee.currency}
        </p>
      )}
      {summary.destinationAddress && (
        <p>Deposit address: {summary.destinationAddress}</p>
      )}
      {summary.transactionHash && <p>Tx: {summary.transactionHash}</p>}
      {summary.status && <p>Status: {summary.status}</p>}
      {summary.sessionId && <p>Session: {summary.sessionId}</p>}
      {summary.from && <p>From: {summary.from}</p>}
      {summary.to && <p>To: {summary.to}</p>}

      <button type="button" onClick={() => void client.restartSession()}>
        Start another payment
      </button>
    </div>
  )
}

useExchangePayCompletedTransactionSummary and the useOnExchangePay* event hooks are provider-free and can mount outside ExchangePayContextProvider.

Putting it together

Wire the steps as screens (or steps in one page):

  1. Wrap with ExchangePayContextProvider
  2. ProviderPicker → user picks an exchange
  3. DepositForm → amount + currency → createOrder() → go to checkout
  4. Checkout → QR / links + countdown → completed / expired
  5. Show summary → restartSession() before another deposit

Deep dives: ExchangePayContextProvider, useExchangePayProviders, useExchangePaySupportedCurrencies, useExchangePayCurrency, useExchangePayAmount, useExchangePaySelection, useExchangePayOrder, Expiry & countdown, Summary, Event hooks, Errors.