Withdraw & export

Two exits for a player’s funds: withdraw sends some tokens out to any Solana address (cash-out to an exchange, pay a friend), while export hands the player the private key to their whole wallet so they can walk away into full self-custody. Both are drop-in components — mount them under the wallet provider and they wire themselves to the signed-in player.

WalletWithdraw

A complete send form: token picker, amount input with a MAX button, address and balance validation, a confirm button, and the on-chain transaction signature on success. It reads the player’s balances itself and only submits once every check passes.

TS
import { WalletWithdraw } from "@iamgame/wallet-sdk";

export function WithdrawPanel() {
  return (
    <WalletWithdraw
      onSuccess={(signature) => {
        console.log("sent, tx:", signature);
      }}
      onError={(err) => {
        console.error("withdraw failed:", err.message);
      }}
    />
  );
}

It renders nothing while the player is signed out. Once signed in it shows their tokens and available balance automatically.

Props

PropTypeWhat it does
mintstringRestrict to a single token. Omit to let the player pick SOL or any SPL token they hold.
onSuccess(signature: string) =&gt; voidCalled with the confirmed tx signature after a successful send.
onError(error: Error) =&gt; voidCalled on validation or send failure (see the note below).
labelstringHeader text. Default "Withdraw".
themeWalletWithdrawThemeColour / radius / font overrides.

To lock the form to one currency — say a USDC-only cash-out — pass mint. The token picker disappears and the form works entirely in that token:

TS
<WalletWithdraw
  mint="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"  // USDC
  label="Cash out USDC"
  onSuccess={(sig) => toast(`Sent — ${sig.slice(0, 8)}…`)}
/>

What the component handles for you

  • Address validation — rejects anything that isn’t a valid base58 Solana pubkey before submitting.
  • Amount + balance checks — parses the decimal amount into base units and blocks a send that exceeds the available balance.
  • MAX with a fee reserve — for SOL, MAX leaves ~0.005 SOL behind so the network fee can be paid; for SPL tokens MAX is the full balance.
  • Fee sponsorship — the send goes through the signer, which sponsors the network fee where applicable, so a player with USDC but no SOL can still withdraw.
  • Double-submit guard + idempotency — a fresh idempotency key per submission and a hard busy-guard mean a double-tap never sends twice.
A returned failure is not a success
The withdraw API can return HTTP 200 with status: "failed" when the on-chain send didn’t land (e.g. no SOL for the network fee). The component treats that as a failure — it shows the error and calls onError, not onSuccess. If you’re building custom UI over the client instead of using the component, check result.status yourself; don’t assume a resolved promise means the funds moved.

Theming

Same token set as the other components, plus a danger colour for the error state:

TS
<WalletWithdraw
  theme={{
    background: "#0d0e11",
    foreground: "#f5f5f4",
    muted: "#8a8577",
    surface: "#16181d",
    border: "#26282e",
    primary: "#d4a537",   // submit + MAX buttons
    danger: "#ef4444",    // error banner
    radius: "0.75rem",
  }}
/>

WalletExport — self-custody

The IAMGame wallet is custodial by default — the key lives in secure hardware on the signer, not in the browser (see Core concepts). Export is the escape hatch: it reveals the wallet’s private key to the player once, deletes that key from our servers, and provisions the player a fresh custodial wallet for future play — with the remaining balance transferred over automatically. After export the player fully owns the old key; they can import it into any Solana wallet.

TS
import { WalletExport } from "@iamgame/wallet-sdk";

export function ExportPanel() {
  return (
    <WalletExport
      onSuccess={(newWalletAddress) => {
        console.log("player exported; new wallet:", newWalletAddress);
      }}
      onError={(err) => console.error("export failed:", err.message)}
    />
  );
}

The flow the component runs

WalletExport is a guided three-step flow — you don’t wire any of it:

StepWhat the player sees
PreflightAn eligibility check: current balance vs the minimum required, and the amount bootstrapped to the new wallet. If the balance is too low the button is disabled with a reason and a Re-check button.
ConfirmA hard “are you sure?” warning — the old key is revealed then deleted, a new wallet is created.
RevealedThe private key shown exactly once with a copy button, plus the new wallet address and the bootstrap tx signature.
Why the preflight balance gate exists
Creating the player’s replacement wallet and transferring their balance to it costs a small amount of on-chain rent + fees (the bootstrap). The preflight makes sure the wallet holds enough to cover it before anyone commits — that’s why a near-empty wallet can be blocked from exporting until it’s topped up. The gate values (currentBalanceLamports, minBalanceLamports, bootstrapLamports, allowed, reason) come straight from the server preflight.
The key is shown once — and it is real money
There is no “show again.” Once the reveal screen is dismissed the key is gone from our side. Make sure the player copies and stores it before they navigate away; the component gives them a copy button and a “save this securely” warning, but the surrounding UX (don’t auto-close, don’t screenshot-prompt) is on you.

Props

PropTypeWhat it does
onSuccess(newWalletAddress: string) =&gt; voidCalled after a successful export with the address of the freshly provisioned wallet.
onError(error: Error) =&gt; voidCalled if the export fails.
labelstringHeader text. Default "Export Wallet".
themeWalletExportThemeColour / radius / font overrides (includes a danger colour for the warnings).

Where to go next