Deposit & on-ramp
A player funds their custodial wallet by getting SOL or USDC to their wallet address on Solana. There are three surfaces for that, and you pick by where the player’s money starts:
| Surface | Player already has… | You render |
|---|---|---|
| Receive card | Crypto in an exchange or another wallet | WalletDeposit — their address + QR to send to |
| On-ramp | Only fiat (a card / bank) | An external provider redirect to buy crypto |
| Transfer-in | A connected external wallet (Phantom, etc.) | useWalletTransferIn + buildSol/SplTransferIn |
The receive card: WalletDeposit
The default surface. WalletDeposit shows the signed-in player’s wallet address in full (never truncated — deposits must be verifiable char-by-char), with a copy button and an optional QR slot. It’s purely presentational: the transfer is made by the sender (an exchange withdrawal or another wallet), so there is nothing to submit. Drop it in and you’re done.
import { WalletDeposit } from "@iamgame/wallet-sdk";
export function DepositPanel() {
return <WalletDeposit label="Add funds" />;
}It renders nothing until the player is signed in (there’s no address yet), so it’s safe to mount anywhere under the provider.
Adding a QR code
The SDK doesn’t bundle a QR library (keeps the browser bundle small). Pass your own via renderQr — it receives the address string and returns whatever element you want:
import { WalletDeposit } from "@iamgame/wallet-sdk";
import { QRCodeSVG } from "qrcode.react";
export function DepositPanel() {
return (
<WalletDeposit
label="Deposit"
renderQr={(address) => <QRCodeSVG value={address} size={160} />}
onCopy={(address) => console.log("copied", address)}
/>
);
}Props & theming
| Prop | Type | Default |
|---|---|---|
label | string | "Deposit" |
instructions | ReactNode | null | Built-in “send SOL or SPL tokens…” copy (pass null to hide) |
renderQr | (address: string) => ReactNode | omitted (no QR) |
showCopy | boolean | true |
onCopy | (address: string) => void | — |
theme | WalletDepositTheme | light default |
theme takes the same token set as the other wallet components — override the ones you care about to match your game:
<WalletDeposit
theme={{
background: "#0d0e11",
foreground: "#f5f5f4",
muted: "#8a8577",
surface: "#16181d",
border: "#26282e",
primary: "#d4a537", // used for the "Copied" state
radius: "0.75rem",
}}
/>useWallet() gives you wallet.address directly. See The wallet in your UI.On-ramp: send the player out to buy crypto
If the player has no crypto yet — only a card or bank — they need an on-ramp: a third-party provider (MoonPay, Transak, Ramp, Coinbase, and regional rails) that sells them USDC and delivers it to a Solana address. This is app-owned UX, not an SDK component: it is click-to-redirect only. You show the provider, open its buy page in a new tab (or the Telegram external browser), and the provider sends USDC to the player’s wallet address. No KYC, no SDK integration, no funds callback on your side — the deposit simply lands on-chain and the balance hook sees it, exactly like the receive card.
The one thing you own is which providers to surface, ideally by the player’s country (UPI-first in India, mobile-money in Africa, cards in the US/EU). Keep that in a simple registry and pass the wallet address into the provider URL:
import { useWallet, getTelegramWebApp } from "@iamgame/wallet-sdk";
// A tiny app-owned registry — link straight to each provider's buy page.
const PROVIDERS = [
{ name: "MoonPay", url: "https://www.moonpay.com/buy/usdc?walletAddress={address}" },
{ name: "Transak", url: "https://global.transak.com?walletAddress={address}" },
{ name: "Ramp", url: "https://ramp.network/buy?userAddress={address}" },
];
function openExternal(url: string, address: string) {
const finalUrl = url.replace("{address}", encodeURIComponent(address));
const tg = getTelegramWebApp() as (ReturnType<typeof getTelegramWebApp> & { openLink?: (u: string) => void }) | null;
if (tg?.openLink) tg.openLink(finalUrl); // Telegram external browser
else window.open(finalUrl, "_blank", "noopener,noreferrer");
}
export function OnRampButtons() {
const wallet = useWallet();
if (!wallet?.address) return null;
return (
<div>
<p>Buy USDC and send it to your wallet:</p>
{PROVIDERS.map((p) => (
<button key={p.name} onClick={() => openExternal(p.url, wallet.address)}>
{p.name}
</button>
))}
</div>
);
}WalletDeposit component is the in-wallet receive surface (“here’s my address, send to it”). The on-ramp is a redirect to an external provider to buy crypto. A good funding screen shows both: the on-ramp for card-only players, the receive card for players who already hold crypto.Transfer-in: move funds from a connected wallet
When the player already has a browser wallet connected (Phantom, Solflare, Backpack) and wants to move funds from it into their IAMGame wallet, build the transfer for them. useWalletTransferIn() gives you the destination (their IAMGame wallet address), and the buildSolTransferIn / buildSplTransferIn helpers build an unsigned Solana transaction for the player’s external wallet to sign and submit.
These helpers use dependency injection for @solana/web3.js and @solana/spl-token — the SDK never bundles them, you pass in the constructors from the copy your game frontend already has. The helper returns an unsigned, base64-serialized transaction; your external-wallet adapter signs and sends it.
import { useWalletTransferIn, buildSplTransferIn } from "@iamgame/wallet-sdk";
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import {
TOKEN_PROGRAM_ID,
createTransferCheckedInstruction,
getAssociatedTokenAddressSync,
createAssociatedTokenAccountInstruction,
getMint,
} from "@solana/spl-token";
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
export function useDepositFromExternal() {
// destinationAddress = the signed-in player's IAMGame wallet.
const { destinationAddress } = useWalletTransferIn();
async function buildTx(fromAddress: string, usdcMicroAmount: string) {
if (!destinationAddress) throw new Error("Not signed in");
// Returns an unsigned base64 tx — hand this to the external wallet to sign + send.
return buildSplTransferIn(
{ Connection, PublicKey, Transaction, TOKEN_PROGRAM_ID,
createTransferCheckedInstruction, getAssociatedTokenAddressSync,
createAssociatedTokenAccountInstruction, getMint },
{
fromAddress, // the player's external wallet
toAddress: destinationAddress, // their IAMGame wallet
mint: USDC_MINT,
amount: usdcMicroAmount, // base units, e.g. "1000000" = 1 USDC
rpcUrl: "https://api.mainnet-beta.solana.com",
},
);
}
return { destinationAddress, buildTx };
}For a plain SOL top-up use buildSolTransferIn instead — same shape, no SPL deps, and lamports in place of mint + amount:
import { buildSolTransferIn } from "@iamgame/wallet-sdk";
import { Connection, PublicKey, Transaction, SystemProgram } from "@solana/web3.js";
const unsignedTx = await buildSolTransferIn(
{ Connection, PublicKey, Transaction, SystemProgram },
{
fromAddress: externalWalletPubkey,
toAddress: destinationAddress, // from useWalletTransferIn()
lamports: "500000000", // 0.5 SOL
rpcUrl: "https://api.mainnet-beta.solana.com",
},
);buildSplTransferIn throws if you don’t pass the five @solana/spl-token helpers (TOKEN_PROGRAM_ID, createTransferCheckedInstruction, getAssociatedTokenAddressSync, createAssociatedTokenAccountInstruction, getMint). If the destination token account (ATA) doesn’t exist yet, the helper adds a create instruction and the sender pays the ~0.002 SOL rent.Which one do I use?
- Default to
WalletDeposit— it covers every player who can get crypto to an address (exchange withdrawal, another wallet, or an on-ramp that delivers to the shown address). - Add on-ramp buttons alongside it so card-only players have a path to buy.
- Reach for transfer-in helpers only when the player has an external wallet connected in your app and you want a one-click “move funds in” button instead of copy-paste.
Where to go next
- The wallet in your UI — reading balance and address, mounting the components.
- Withdraw & export — sending funds out and self-custody export.
- Custodial treasury — the server side: crediting real-money balances against confirmed deposits.