The Deposit SDK (@swype-org/deposit) opens Blink’s hosted deposit flow in a modal iframe overlay. On mobile viewports (480px or narrower), the iframe renders full-screen for an app-like experience. The SDK handles the signer call, iframe lifecycle, passkey/WebAuthn delegation, and completion detection via postMessage.
Install
npm install @swype-org/deposit
yarn add @swype-org/deposit
pnpm add @swype-org/deposit
JavaScript
import { Deposit, DepositError, getDisplayMessage } from '@swype-org/deposit';
const deposit = new Deposit({
signer: '/api/sign-payment',
});
document.getElementById('transfer-btn')!.addEventListener('click', async () => {
try {
const { transfer: result } = await deposit.requestDeposit({
amount: 50,
chainId: 8453,
address: '0x1a5FdBc891c5D4E6aD68064Ae45D43146D4F9f3a',
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
});
console.log('Transfer complete:', result.id, result.status);
} catch (error) {
if (error instanceof DepositError) {
showErrorToUser(getDisplayMessage(error));
}
}
});
React hook
import { useBlinkDeposit } from '@swype-org/deposit/react';
function TransferButton() {
const { status, result, error, displayMessage, requestDeposit } = useBlinkDeposit({
signer: '/api/sign-payment',
});
const handleTransfer = () => {
requestDeposit({
amount: 50,
chainId: 8453,
address: userWalletAddress,
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
});
};
return (
<>
<button onClick={handleTransfer} disabled={status === 'signer-loading'}>
{status === 'signer-loading' ? 'Preparing...' : 'Deposit $50'}
</button>
{error && <p className="error">{displayMessage}</p>}
{result && <p>Transfer {result.transfer.id} complete!</p>}
</>
);
}
The hook returns reactive status, result, error, displayMessage, and isActive values, plus requestDeposit, focus, and close actions. It manages the Deposit lifecycle and cleans up on unmount.
You don’t have to design your own button. The SDK ships BlinkDepositButton, a prebuilt Blink-branded button — a black pill with a “Deposit stablecoins / In a Blink” label and USDC/USDT coin marks. It is self-contained (inline styles, data-URI icons, no CSS imports) and fills the width of its container.
import { BlinkDepositButton, useBlinkDeposit } from '@swype-org/deposit/react';
function Checkout({ userWalletAddress }: { userWalletAddress: string }) {
const { status, error, displayMessage, requestDeposit } = useBlinkDeposit({
signer: '/api/sign-payment',
});
return (
<>
<BlinkDepositButton
onClick={() =>
requestDeposit({
amount: 50,
chainId: 8453,
address: userWalletAddress,
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
})
}
loading={status === 'signer-loading'}
/>
{error && <p className="error">{displayMessage}</p>}
</>
);
}
The button is presentation-only — wire onClick to requestDeposit yourself, and pass disabled or loading to reflect your app’s state. Its branding is intentionally fixed; if you need a custom look, keep building your own button as shown above. See the BlinkDepositButton reference for the full props table.
Configuration
const deposit = new Deposit({
// Required: URL string or custom async function
signer: '/api/sign-payment',
// Base URL of the hosted payment webview app.
// Default: 'https://pay.blink.cash'
webviewBaseUrl: 'https://pay.blink.cash',
// Origin for postMessage validation. Derived from webviewBaseUrl when omitted.
hostedFlowOrigin: 'https://pay.blink.cash',
// DOM element to mount the iframe overlay into. Default: document.body
containerElement: document.getElementById('transfer-root')!,
// Max ms to wait for signer response. Default: 15000
signerTimeoutMs: 15_000,
// Max ms for entire flow (signer + user completion). No limit by default.
flowTimeoutMs: 300_000,
// Allow the hosted flow to show the full widget (deposit address options
// alongside Blink one tap). Set to false to send users straight to the
// Blink one-tap flow. Default: true
enableFullWidget: true,
// Enable debug logging. Default: false
debug: false,
});
By default the hosted flow can open with the full widget — a deposit options entry screen that offers deposit addresses (pay from an exchange or any wallet) alongside Blink’s one-tap deposit. Set enableFullWidget: false to skip it and send every user straight to the Blink one-tap flow.
The full widget only appears when both your SDK config and your Blink merchant account have it enabled. If it is disabled on your merchant account, the hosted flow stays in one-tap mode regardless of this option — contact Blink to change your account configuration.
Custom signer function
If you need full control over the HTTP call to your signer (custom headers, auth tokens, different HTTP method), pass a function instead of a URL:
import type { SignerFunction } from '@swype-org/deposit';
const deposit = new Deposit({
signer: async (data) => {
const res = await fetch('/api/sign-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getSessionToken()}`,
},
body: JSON.stringify(data),
});
if (!res.ok) throw new Error(`Signer error: ${res.status}`);
return res.json();
},
});
Deposit request fields
| Field | Type | Required | Description |
|---|
amount | number | Yes | USD amount to deposit (> 0). |
chainId | number | Yes | Destination chain ID. Dynamic per user. For example, 8453 for Base or 792703809 for Solana. |
address | string | Yes | Destination wallet address. For the web SDK this can be an EVM address or a Solana address, depending on the destination chain. |
token | string | Yes | Destination token identifier. Use an EVM token contract address on EVM chains or an SPL mint address on Solana. |
callbackScheme | string | null | No | Always null for browser integrations. Reserved for native app deep links. |
reference | string | No | Merchant order/invoice ID for reconciliation. |
metadata | Record<string, string> | No | Arbitrary key-value pairs forwarded to your signer. |
The destination chainId, address, and token are not static merchant configuration. They are set dynamically per transaction, typically based on the user’s embedded wallet.
Choose destination values from Blink’s active routing catalog. See Supported Networks and Wallets for the current wallet, chain, token, and fee model details.
Deposit result
When the payment completes, the SDK returns a DepositResult:
interface DepositResult {
transfer: TransferSummary;
idempotencyKey?: string;
preview?: {
amount: number;
chainId: number;
address: string;
token: string;
};
}
interface TransferSummary {
id: string;
status: string;
amount?: { amount: number; currency: string };
destinations?: Array<{
chainId: string;
address: string;
token?: { address?: string; symbol?: string };
}>;
}
Status flow
| Status | Meaning |
|---|
idle | No active flow. |
signer-loading | Calling the merchant signer endpoint. |
iframe-active | Hosted flow iframe is open, waiting for user to complete payment. |
completed | Transfer succeeded. |
error | Something failed. |
deposit.on('status-change', (status) => console.log('Status:', status));
deposit.status; // current status
deposit.result; // last DepositResult (when completed)
deposit.error; // last DepositError (when error)
deposit.isActive; // true during signer-loading or iframe-active
Error handling
See Error Codes for the full reference. Every error is a DepositError with a machine-readable code:
import { DepositError, getDisplayMessage } from '@swype-org/deposit';
try {
await deposit.requestDeposit({ /* ... */ });
} catch (err) {
if (err instanceof DepositError) {
showToast(getDisplayMessage(err));
console.error(err.code, err.message);
}
}
Lifecycle
// Close the deposit iframe without waiting for completion
deposit.close();
// Tear down and release all resources (call on unmount / page unload)
deposit.destroy();
Pass merchant-specific data through the flow for reconciliation:
await deposit.requestDeposit({
amount: 50,
chainId: 8453,
address: '0x...',
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
reference: 'order-123',
metadata: { invoiceId: 'INV-456', customerId: 'cust-789' },
});
The reference and metadata are forwarded to your signer endpoint so you can correlate the payment with your internal records.
Security
The deposit UI loads live from https://pay.blink.cash. Before going live, lock
the iframe origin with a Content-Security-Policy on your page and learn how to
verify the build:
Content-Security-Policy: frame-src https://pay.blink.cash; script-src 'self';
See Security & Content Integrity for the full model,
including passkey delegation and build-manifest verification.
Mobile browser considerations
- Full-screen iframe: On viewports 480px or narrower, the transfer iframe automatically renders full-screen with no border radius.
- Passkey / WebAuthn: The SDK transparently handles WebAuthn ceremonies between the cross-origin iframe and the parent page. No additional configuration is needed.
callbackScheme: Always pass null (or omit) for browser integrations. Reserved for native app deep-link flows.
- Viewport meta tag: Ensure your page includes a proper viewport meta tag:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />