type DepositErrorCode = 'DEPOSIT_DISMISSED' | 'CHECKOUT_DISMISSED' | 'SIGNER_REQUEST_FAILED' | 'SIGNER_NETWORK_ERROR' | 'SIGNER_RESPONSE_INVALID' | 'SIGNER_TIMEOUT' | 'FLOW_TIMEOUT' | 'INVALID_REQUEST' | 'IDENTIFIERS_UNAVAILABLE'; /** @deprecated Use {@link DepositErrorCode} instead. */ type CheckoutErrorCode = DepositErrorCode; /** * Structured error thrown by the deposit SDK. * * Every error carries a machine-readable {@link code} so merchants can * branch on failure mode without parsing message strings. */ declare class DepositError extends Error { readonly code: DepositErrorCode; constructor(code: DepositErrorCode, message: string); } /** @deprecated Use {@link DepositError} instead. */ declare const CheckoutError: typeof DepositError; /** @deprecated Use {@link DepositError} instead. */ type CheckoutError = DepositError; /** * Return a user-friendly display string for a {@link DepositError}. * * Merchants can show this directly in their UI without maintaining their own * copy for each error code. */ declare function getDisplayMessage(error: DepositError): string; type VerificationErrorCode = /** This palm already verifies a different account at this merchant. The first binding holds. */ 'ALREADY_VERIFIED_OTHER_ACCOUNT' /** A different palm was scanned for an account that already holds one. */ | 'ACCOUNT_ALREADY_VERIFIED' /** The user closed the flow before it finished. */ | 'VERIFICATION_DISMISSED' /** The attempt's 30-minute window passed. */ | 'VERIFICATION_EXPIRED' /** The user cancelled at the provider. */ | 'VERIFICATION_DECLINED' /** The provider could not complete the scan (an expired or replayed code, or an outage). */ | 'VERIFICATION_FAILED' /** The provider restricted this human; neither the palm nor the account can be re-verified. */ | 'VERIFICATION_REVOKED' /** Not available here: the merchant is not enabled, the feature is switched off, or the hosted page predates it. */ | 'VERIFICATION_UNAVAILABLE' | 'SIGNER_REQUEST_FAILED' | 'SIGNER_NETWORK_ERROR' | 'SIGNER_RESPONSE_INVALID' | 'SIGNER_TIMEOUT' | 'FLOW_TIMEOUT' | 'INVALID_REQUEST'; /** * Structured error thrown by {@link Verification}. A sibling of * {@link DepositError} rather than a subclass: the two flows share signer * codes but nothing else, and a merchant branching on `code` should not * be able to confuse a dismissed deposit with a dismissed verification. */ declare class VerificationError extends Error { readonly code: VerificationErrorCode; constructor(code: VerificationErrorCode, message: string); } /** Return a user-friendly display string for a {@link VerificationError}. */ declare function getVerificationDisplayMessage(error: VerificationError): string; /** Current phase of the deposit flow. */ type DepositStatus = 'idle' | 'signer-loading' | 'iframe-active' | 'completed' | 'error'; /** @deprecated Use {@link DepositStatus} instead. */ type CheckoutStatus = DepositStatus; /** Configuration for the {@link Deposit} instance. */ interface DepositConfig { /** * Merchant signer — either a URL string or a custom async function. * * **String:** The SDK sends a `POST` with a JSON `SignerRequest` body to this * URL and expects a `SignerResponse` back. This is the simplest integration. * * **Function:** The SDK calls the function with a `SignerRequest` and expects * a `Promise`. Use this when you need control over the HTTP * method, authentication headers, request transformation, or any other * aspect of the signing request. */ signer: string | SignerFunction; /** * Blink environment to target. * * `'sandbox'` points the hosted flow at the public testnet environment * (`https://pay-sandbox.blink.cash`), where deposits run on test networks * with separate merchant credentials. An explicit {@link webviewBaseUrl} * always takes precedence over this option. * * @default 'production' */ environment?: 'production' | 'sandbox'; /** * Base URL of the hosted payment webview app. * * The SDK appends `merchantId`, `payload`, and `signature` query parameters * to this URL and loads the result in a modal iframe. When set, this * overrides {@link environment}. * * @default 'https://pay.blink.cash' */ webviewBaseUrl?: string; /** * Origin of the hosted payment page. * * Used for `postMessage` origin validation. When omitted the SDK derives it * from {@link webviewBaseUrl}. */ hostedFlowOrigin?: string; /** * DOM element the iframe is mounted into. * * In the default `presentation: 'overlay'` this only changes the overlay's * append parent — the overlay is still `position: fixed; inset: 0` and * covers the viewport regardless of where it hangs in the tree. * * In `presentation: 'embedded'` it is the real mount point: the iframe * renders inline inside this element, and the option is **required**. The * element must be stable across re-renders — moving an iframe in the DOM * reloads it, discarding the warmed-up document and the user's session. * (On mobile, where an embedded flow presents as an overlay instead, this * element is left untouched and the overlay mounts on `document.body` — a * `position: fixed` overlay inside a host's animated dialog would position * against that dialog rather than the viewport.) * * @default document.body */ containerElement?: HTMLElement; /** * How the hosted flow is presented on the host page. * * `'overlay'` (the default) is the classic modal: a full-viewport dim + * blur backdrop with the payment card centered on top, dismissable by * backdrop click or Escape. * * `'embedded'` mounts the iframe **inline** inside {@link containerElement} * — for a payment-method aggregator that lists Blink as one option inside * its own widget. There is no backdrop, no body scroll lock, and no * backdrop/Escape dismissal (the host owns all of that); the flow renders * chrome-less so the host's own panel provides the radius, background and * shadow. The iframe's height follows the flow's content, reported through * the {@link Deposit.on | `resize`} event. * * Requires {@link containerElement}. * * **Desktop only.** On mobile an embedded flow presents as the overlay: a * phone-sized method panel cannot hold a payment journey with a keypad, * wallet lists and a QR code, and the overlay is what a phone user gets from * every other Blink integration. Read the resolved value from * {@link Deposit.presentation} — on `'overlay'` no `resize` event ever fires, * so a panel sized from those reports should be collapsed or skipped. * * Nothing else downgrades it. A *failure* never reaches the overlay (an * older webview keeps the inline presenter), because dropping a full-screen * modal over an aggregator's widget unannounced is worse than the flow not * starting; the mobile choice is deliberate and made before anything renders. * * @default 'overlay' */ presentation?: 'overlay' | 'embedded'; /** * Embedded presentation only: hard cap on the inline iframe's height, in * CSS pixels. Ignored on mobile, where the flow presents as an overlay and * sizes itself against the real viewport. * * Two jobs. It bounds the iframe element, so a tall screen scrolls * internally instead of pushing the host's page around. And it is the * basis viewport-derived sizing inside the flow resolves against — which * is why it must be a budget the host decides, never a measurement of what * the iframe currently is. (An inline iframe sized from its own content * makes `dvh` self-referential; feeding the measured height back in would * make every decorative band inflate to its ceiling.) * * @default Math.round(window.innerHeight * 0.9) */ embedMaxHeightPx?: number; /** * Maximum time in milliseconds to wait for the signer endpoint to respond. * * @default 15000 */ signerTimeoutMs?: number; /** * Maximum time in milliseconds for the entire deposit flow (signer + user * completion). When exceeded the iframe is closed and the promise rejects * with a `FLOW_TIMEOUT` error. `undefined` means no limit. */ flowTimeoutMs?: number; /** * Whether the hosted flow may show the full widget (Deposit Options entry * screen). The full widget only appears when this is enabled (default) AND * the merchant's backend config has it enabled — disabling either turns it * off. * * @default true */ enableFullWidget?: boolean; /** * Overlay layout mode. * * `'fluid'` (the default) spans the host viewport with a transparent * iframe and lets the hosted flow render the card/sheet chrome itself, so * the payment card can size to its content. `'fixed'` forces the legacy * fixed-size iframe (420×600 desktop modal / 79vh mobile sheet) with the * chrome rendered by this SDK — primarily an escape hatch for testing and * for comparing against the pre-0.3.21 behavior. * * @default 'fluid' */ layout?: 'fluid' | 'fixed'; /** * Visual appearance of the hosted payment UI. Nested `{ theme }` object, * mirroring Privy's `appearance` config. `theme: 'light'` renders the light * palette; `'dark'` renders the dark palette; `'system'` follows the user's * OS `prefers-color-scheme`. Passed to the hosted flow as an `appearance` * query param and carried through every Blink surface the flow opens * (top-level setup tab, wallet authorization pages). * * When no theme is passed, the default follows the merchant page's own * declared `color-scheme`: a page declaring `dark` defaults the flow to * dark, a page declaring `light dark` defaults to `system`, and everything * else defaults to light. Pass an explicit theme to override the detection * (`theme: 'light'` pins light even on a dark page). * * @example * new Deposit({ signer, appearance: { theme: 'dark' } }) */ appearance?: { /** Hide deposit entry/status titles when your container supplies its own. More and other navigation titles remain visible. Defaults to false. */ hideDepositTitle?: boolean; /** * Color scheme of the hosted UI. * @default detected from the merchant page's declared `color-scheme`, * falling back to `'light'` */ theme?: 'light' | 'dark' | 'system'; /** * Brand colors for the hosted UI. Blink derives its whole palette from * these — surfaces, the recessed/hover steps, the text ramp, borders, the * button hover and label, the tinted semantic surfaces — so the flow reads * as part of your product rather than a foreign panel inside it. Most * valuable in the embedded presentation, where Blink sits in your own * dialog, but applied on every surface. * * **Opaque hex only** (`#rgb` or `#rrggbb`). Anything else — `rgb()`, a CSS * color name, 8-digit hex with alpha — is dropped with a console error * naming the key: a solid card cannot honour translucency, and a color that * cannot be parsed cannot be contrast-checked. * * Contrast is enforced. Where a derived pair would fall below WCAG AA the * *derived* value is substituted (never your input) and the substitution is * reported in the hosted flow's console. A `colorPrimary` too mid-toned for * either a black or a white label is reported and left alone — only you can * fix that one. * * The scale (sizes, weights, radii) and Blink's own brand marks, semantic * hues and authorization states are not themeable. * * @example * appearance: { * theme: 'light', * variables: { * colorPrimary: '#0f62fe', * colorBackground: '#ffffff', * colorText: '#111111', * }, * } */ variables?: { /** Primary button fill, focus ring, selected states. */ colorPrimary?: string; /** * The card surface. Its luminance also picks the light or dark base for * everything not themeable — shadows, the backdrop scrim, semantic hues — * so a dark surface stays coherent even with `theme: 'light'`. */ colorBackground?: string; /** Body text. The secondary/muted/tertiary ramp is derived from it. */ colorText?: string; /** Error text and the error surface tint. */ colorDanger?: string; /** Hairline borders. Derived from surface + text when omitted. */ colorBorder?: string; }; }; /** * Warm up the hosted payment flow in a hidden iframe as soon as the * `Deposit` instance is constructed (deferred until the host page has * loaded and the main thread is idle). By the time the user starts a * deposit, the flow's JS bundle and auth session are already loaded, so * the payment UI appears near-instantly. * * Costs roughly one page load of bandwidth even if the user never * deposits. Set `false` to opt out and call {@link Deposit.preload} * manually (e.g. on deposit-button hover) — or not at all. * * @default true */ preload?: boolean; /** * Optional merchant id, appended to the warm-up URL so the hosted flow can * prefetch the merchant's config and signing public key before the signer * responds. Purely a performance hint: it is never trusted for * authorization, which always comes from the signed payload returned by * {@link signer}. */ merchantId?: string; /** * When `true`, the SDK logs lifecycle events (signer calls, iframe state, * message reception, errors) to `console.debug`. Useful during integration. * * @default false */ debug?: boolean; } /** @deprecated Use {@link DepositConfig} instead. */ type CheckoutConfig = DepositConfig; /** One place a deposit may be credited: a chain, a token on it, and the wallet address there. */ interface DepositDestination { /** Numeric chain ID (e.g. 8453 for Base, 792703809 for Solana, 728126428 for Tron). */ chainId: number; /** Wallet address on that chain. */ address: string; /** Token contract address (EVM), SPL mint (Solana) or TRC-20 contract (Tron) on that chain. */ token: string; } /** Parameters for a single deposit request. */ interface DepositRequest { /** * USD amount to deposit. Must be > 0 when provided. Pass `null` to let the * user enter the amount inside the hosted flow. */ amount: number | null; /** * Numeric chain ID for the destination (e.g. 8453 for Base, 792703809 for * Solana). Required unless `destinations` is given. */ chainId?: number; /** * Destination wallet address — EVM (0x-prefixed, 40 hex chars) or Solana * (Base58, 32-44 chars). Required unless `destinations` is given. */ address?: string; /** * Token address on the destination chain. * EVM: contract address (e.g. `'0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'` for USDC on Base). * Solana: SPL mint address (e.g. `'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'` for USDC). * Required unless `destinations` is given. */ token?: string; /** * The full destination menu, most-preferred first — REPLACES `chainId`, * `address` and `token` (send one shape or the other, never both). Use it * when your user can be credited at more than one (chain, token, address): * Blink settles every deposit on exactly ONE entry, chosen per deposit from * the asset the user sends. A source that matches an entry's chain and * token settles there directly with no bridge; otherwise the same asset on * another chain is preferred, then the same chain, with an Ethereum entry * ranked last. Ties break on your order. At most 10 entries; no two may * share a chain and token. */ destinations?: DepositDestination[]; /** * Optional custom URL scheme for mobile deep-link flows * (SFSafariViewController / Android Custom Tabs). */ callbackScheme?: string | null; /** * Minimum deposit this merchant will accept, in USD (e.g. `10` or `9.50`). * Rounded UP to whole cents on the wire — a floor must never be advertised * lower than the one enforced. * * Raises Blink's own per-route minimum; it never lowers it. If Blink needs * $5 on the chosen route and you pass `2`, the flow still requires $5 — * below that the deposit cannot physically settle. Pass a higher figure and * yours is the one shown and enforced. * * Applies to both the amount screen (the Deposit button stays disabled * below it) and the manual/QR deposit terms. Unsigned and never sent to * your signer: it is a merchant business rule, not a security boundary. */ minimumDeposit?: number | null; /** Merchant order or invoice ID for reconciliation. */ reference?: string; /** Arbitrary key-value pairs forwarded to the signer endpoint. */ metadata?: Record; /** * The user's current balance on YOUR platform, in USD — display only. * * When provided, the hosted flow shows it under the deposit header as * "{Merchant} balance: $X.XX" so the user tops up with their platform * balance in view. It is never part of the signed payload and is never * sent to your signer, so passing it requires no signer changes — and it * cannot influence the transfer amount or destination. * * Must be a finite number, `>= 0` and below `1e12`. The SDK floors it to * whole cents before sending (`20.789` renders as `$20.78`); anything else * is dropped with a `console.error` naming the field, and the deposit * proceeds without the subtitle. The hosted flow independently re-validates * whatever it receives, so the value is purely cosmetic. When omitted, the * hosted flow may fall back to showing the destination wallet's on-chain * balance instead. */ balance?: number; } /** Result returned when a deposit completes successfully. */ interface DepositResult { transfer: TransferSummary; /** Idempotency key from the signer response, if available. */ idempotencyKey?: string; /** Signer preview echoed back for merchant-side display / logging. */ preview?: { amount: number; chainId: number; address: string; token: string; /** The menu, when the deposit was requested with one. */ destinations?: DepositDestination[]; }; } /** Subset of transfer data returned from the hosted flow on completion. */ interface TransferSummary { id: string; status: string; amount?: { amount: number; currency: string; }; destinations?: Array<{ chainId: string; address: string; token?: { address?: string; symbol?: string; }; }>; } /** Shape returned by the merchant signer. */ interface SignerResponse { merchantId: string; payload: string; signature: string; expiresAt?: string; preview: { amount: number; /** The first (preferred) destination when a menu was signed. */ chainId: number; address: string; token: string; /** Echo of the signed menu, when one was requested. */ destinations?: DepositDestination[]; idempotencyKey: string; }; } /** Data passed to the merchant signer describing the payment to sign. */ interface SignerRequest { /** * USD amount to sign over. `null` indicates the user will enter the amount * inside the hosted flow. */ amount: number | null; /** Present for a single-destination request. Absent when `destinations` is sent. */ chainId?: number; address?: string; token?: string; /** * Present for a multi-destination request — sign it into the payload as * `destinations` and do NOT also sign `chainId`/`address`/`token`. Verify * the caller owns EVERY address in it before signing. */ destinations?: DepositDestination[]; callbackScheme: string | null; url: string; version: 'v1'; reference?: string; metadata?: Record; } /** * Async function that receives a {@link SignerRequest} and returns a * {@link SignerResponse}. Use this when you need full control over how * the signing request is made (custom HTTP method, auth headers, etc.). */ type SignerFunction = (data: SignerRequest) => Promise; /** Current phase of a {@link Verification} flow. */ type VerificationStatus = 'idle' | 'signer-loading' | 'iframe-active' | 'completed' | 'error'; /** Configuration for the {@link Verification} instance. */ interface VerificationConfig { /** * Merchant signer — either a URL string or a custom async function. * * Called with a {@link VerificationSignerRequest} (`kind: * 'human-verification'`). It must run **behind your own user auth**: verify * the caller's session, then sign a grant naming that user as * `merchantUserId`. The SDK never sends a user id — a grant that took its * subject from the browser would let anyone bind any account to their own * palm. * * **String:** the SDK `POST`s the request as JSON and expects a * {@link VerificationSignerResponse}. Pass a function to add your own auth * headers. */ signer: string | VerificationSignerFunction; /** * Blink environment to target. `'sandbox'` points at * `https://pay-sandbox.blink.cash`. An explicit {@link webviewBaseUrl} * always wins. * * @default 'production' */ environment?: 'production' | 'sandbox'; /** * Base URL of the hosted Blink app; the flow renders at `/verify` under * it. Overrides {@link environment}. * * @default 'https://pay.blink.cash' */ webviewBaseUrl?: string; /** Origin of the hosted page, for `postMessage` validation. Derived from {@link webviewBaseUrl} when omitted. */ hostedFlowOrigin?: string; /** * DOM element the overlay is appended to. The overlay is still * `position: fixed; inset: 0` and covers the viewport. * * @default document.body */ containerElement?: HTMLElement; /** Maximum time to wait for the signer, in milliseconds. @default 15000 */ signerTimeoutMs?: number; /** * Maximum time for the whole flow (signer + scan), in milliseconds. The * hosted session itself lasts 30 minutes; this bounds the promise. When * exceeded the overlay closes and the promise rejects with `FLOW_TIMEOUT`. * `undefined` means no limit. */ flowTimeoutMs?: number; /** * Visual appearance of the hosted page. `theme: 'system'` follows the * user's OS `prefers-color-scheme`; absent means light. */ appearance?: { theme?: 'light' | 'dark' | 'system'; }; /** When `true`, the SDK logs lifecycle events to `console.debug`. @default false */ debug?: boolean; } /** Optional inputs to {@link Verification.verifyHuman}, forwarded to the signer verbatim. */ interface VerifyHumanOptions { /** Merchant reference for reconciliation. */ reference?: string; /** Arbitrary key-value pairs forwarded to the signer endpoint. */ metadata?: Record; } /** * Data passed to the merchant signer for a human verification. * * Distinguished from a deposit {@link SignerRequest} by `kind`, so a signer * that serves both branches on it; a deposit request never carries `kind`. */ interface VerificationSignerRequest { kind: 'human-verification'; /** Always `null` from the web SDK; the mobile SDK sends the app's scheme. */ callbackScheme: string | null; url: string; version: 'v1'; reference?: string; metadata?: Record; } /** * Shape returned by the merchant signer for a human verification: the grant. * * `payload` is the base64url JSON * `{ version: 'v1', kind: 'human-verification', merchantUserId, callbackScheme, * signatureTimestamp, expiresAt }` and `signature` is ECDSA P-256 / SHA-256 * over that exact string, base64url — the same scheme as a deposit payload. */ interface VerificationSignerResponse { merchantId: string; payload: string; signature: string; expiresAt?: string; } type VerificationSignerFunction = (data: VerificationSignerRequest) => Promise; /** * What {@link Verification.verifyHuman} resolves with. * * **A hint.** The page that produced it runs in the user's own browser. Your * server reads the truth from `GET /v1/identity/human-verifications?merchantUserId=…` * with your merchant key, and that is the only answer to unlock on. */ interface VerificationResult { /** The Blink session that bound the user, or `null` when they were already verified. */ sessionId: string | null; status: 'verified'; } export { type CheckoutConfig as C, type DepositStatus as D, type SignerFunction as S, type TransferSummary as T, type VerificationStatus as V, type DepositResult as a, DepositError as b, type DepositConfig as c, type DepositRequest as d, type VerificationResult as e, VerificationError as f, type VerificationConfig as g, type VerifyHumanOptions as h, CheckoutError as i, type CheckoutErrorCode as j, type CheckoutStatus as k, type DepositDestination as l, type DepositErrorCode as m, type SignerRequest as n, type SignerResponse as o, type VerificationErrorCode as p, type VerificationSignerFunction as q, type VerificationSignerRequest as r, type VerificationSignerResponse as s, getDisplayMessage as t, getVerificationDisplayMessage as u };