Skip to content

Example

End-to-end Coinbase OAuth withdraw with client.coinbase: connect, pick balance / network, withdraw (with 2FA when required), show summary.

Route here when the payment method has provider: coinbase and type: exchange_oauth. Session must be active before startWithdrawal. After a completed payment, call restartSession() before starting again.

1. Set up the client

ts
import {
  createSwappedConnectClient,
  IntegrationProvider,
  IntegrationProviderType,
} from '@swapped/connect-sdk';

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

await client.loadSession();

2. Confirm Coinbase is available

ts
const methods = await client.paymentMethods.get({
  type: IntegrationProviderType.ExchangeOauth,
});

const coinbaseMethod = methods.find(
  method =>
    method.provider === IntegrationProvider.Coinbase &&
    method.type === IntegrationProviderType.ExchangeOauth,
);

if (!coinbaseMethod) {
  throw new Error('Coinbase is not available for this session');
}

3. Connect (OAuth popup)

Wait for token restore, then connect. Revalidate with ensureSession() when returning to the flow.

ts
await client.coinbase.ready(); // finishes restoring any stored token

if (!client.coinbase.isConnected()) {
  const connected = await client.coinbase.connect();
  if (!connected) {
    // User closed the popup or auth failed — show retry
    // Watch for OAUTH_POPUP_BLOCKED / OAUTH_POPUP_CLOSED
  }
}

const stillValid = await client.coinbase.ensureSession();
if (!stillValid) {
  // Token expired — emit coinbase:sessionExpired; call connect() again
  await client.coinbase.connect();
}

client.coinbase.onSessionExpired(() => {
  // Send user back to the Connect step
});

4. Load balances and let the user pick one

Do not hardcode a currency. Show a picker from getBalances() and continue with the selected row.

ts
const balances = await client.coinbase.getBalances();

// Render a picker — prefer eligible rows; optionally show ineligible with a hint
const balance = await pickBalance(balances); // your UI: user selects a CoinbaseBalance

if (!balance.eligible) {
  throw new Error('Selected balance is not eligible');
}

5. Optional funding tokens

After a balance is selected, optionally let the user include other balances that can fund the withdraw. Funding helpers take balance.raw rows.

ts
const raw = balances.map(item => item.raw);

const candidates = client.coinbase.getFundingTokens(raw, balance.currency);
const defaults = client.coinbase.getDefaultFundingTokens(raw, balance.currency);

// Render toggles from candidates; start from defaults or let the user choose
const fundingTokens = await pickFundingTokens(candidates, defaults); // your UI

const selection = client.coinbase.getSelectionAggregatedBalance({
  balances: raw,
  currency: balance.currency,
  fundingTokens,
});

6. Load networks and let the user pick one

ts
const networks = await client.coinbase.getNetworks({
  currency: balance.currency,
  accountName: balance.id,
});

// Render a picker — prefer eligible networks
const network = await pickNetwork(networks); // your UI: user selects a CoinbaseNetworkItem

if (!network.eligible) {
  throw new Error('Selected network is not eligible');
}

7. Amount + limits

Collect the amount from the user. Use min / max for the field UI; optional validateWithdrawalAmount for inline errors. Viable above_spendable amounts are auto-adjusted by startWithdrawal (see withdrawal).

ts
const minAmount = await client.coinbase.getMinWithdrawalAmount({
  symbol: balance.currency,
  exchangeRate: balance.exchangeRate,
  network: network.id,
});

const maxAmount = client.coinbase.getMaxWithdrawableAmount({
  balance: selection.balance,
  exchangeRate: balance.exchangeRate,
  symbol: balance.currency,
});

const amount = await askAmount({ minAmount, maxAmount }); // your UI

const validation = await client.coinbase.validateWithdrawalAmount({
  amount,
  balance: selection.balance,
  exchangeRate: balance.exchangeRate,
  symbol: balance.currency,
  network: network.id,
});

if (!validation.ok && validation.reason !== 'above_spendable') {
  // Show validation.reason on the amount field
}

8. Start withdrawal

Respect cooldown on Start (default ~30s). Confirm is not blocked by cooldown. When the amount is above the fee-reserve max, the SDK caps it and awaits onAmountAdjusted if provided.

ts
const { isInCooldown, remainingMs } = client.coinbase.getCooldown();
if (isInCooldown) {
  // Disable Start — show remainingMs
}

const startResult = await client.coinbase.startWithdrawal(
  {
    amount,
    currency: balance.currency,
    name: balance.name, // CoinbaseBalance.name — required
    network: network.id,
    fundingTokens, // from the funding step
  },
  {
    onAmountAdjusted: ({ formatted }) => {
      // Toast / banner — withdrawal continues with the adjusted amount.
      showToast(
        `Amount reduced to ${formatted.adjustedAmount} for the $${formatted.feeReserveFiat} network fee reserve.`,
      )
    },
  },
);

if (startResult.status === 'idle') {
  // Cancelled via onAmountAdjusted returning false (if you used a confirm dialog)
}

if (startResult.status === 'requires2fa') {
  // go to 2FA step
}

9. Confirm 2FA

The 2FA code can come from SMS, email, or an authenticator app and is typically 6–7 characters — do not restrict the input to exactly 6 digits. Wrong 2FA often returns requires2fa again. Passkeys are not supported → COINBASE_PASSKEY_NOT_SUPPORTED.

ts
const code = await askTwoFactorCode(); // your UI
const confirmResult = await client.coinbase.confirmWithdrawal(code);

if (confirmResult.status === 'requires2fa') {
  // Wrong code — keep 2FA UI and show feedback
}
// 'completed' → done; thrown errors → surface and retry when allowed

// User abandons 2FA (clears local pending only — not Coinbase-side)
// client.coinbase.cancelWithdrawal()

10. Summary and restart

ts
client.coinbase.onWithdrawalCompleted(() => {
  const summary = client.coinbase.getCompletedTransactionSummary();
  console.log(summary);
  // Show success UI
});

// Before another payment on this session:
await client.restartSession();

Next

TopicDocs
OAuth / ensureSessionConnection
Balance listBalances
Funding selectionFunding tokens
Network pickerNetworks
Min / max validationLimits
Start / confirm / cancelWithdrawal
Start-button lockoutCooldown
Success screen dataSummary
Lifecycle listenersEvents
Error codesErrors