@unifold/headless-react 0.1.75 → 0.1.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -117,8 +117,8 @@ stream (they only listen — neither starts nor stops the session):
117
117
 
118
118
  ```ts
119
119
  // Generic primitive over the lifecycle state machine:
120
- await session.waitForStatus('processing'); // live activity detected
121
- await session.waitForStatus('ready'); // addresses ready / all settled
120
+ await session.waitForStatus('processing'); // live activity detected
121
+ await session.waitForStatus('ready'); // addresses ready / all settled
122
122
 
123
123
  // The 90% case — mirrors beginDeposit()'s promise contract
124
124
  // (resolve on success, reject on failure):
@@ -130,7 +130,7 @@ try {
130
130
  }
131
131
  ```
132
132
 
133
- Both accept `{ signal }` — an `AbortSignal` cancels the *wait*, never the
133
+ Both accept `{ signal }` — an `AbortSignal` cancels the _wait_, never the
134
134
  session (a deposit isn't cancelable: once the user sends funds, they arrive
135
135
  whether or not anyone is awaiting). For a deadline, compose the platform
136
136
  primitive — `waitForSuccess({ signal: AbortSignal.timeout(60_000) })` — and
@@ -147,7 +147,7 @@ its own `direct_execution.succeeded` event.
147
147
 
148
148
  **Live outcomes only.** The 60s lookback window exists to catch deposits sent
149
149
  moments before the session started — it only admits executions still
150
- *in-flight* at first sight (their settlement then fires live). An execution
150
+ _in-flight_ at first sight (their settlement then fires live). An execution
151
151
  that already settled before the session began is history and never re-fires
152
152
  success/failure events, so reopening a deposit screen right after a success
153
153
  cannot double-credit. Render history with `useExecutions` instead. To credit each deposit, subscribe
@@ -159,9 +159,129 @@ session.on(DepositSessionEventType.EXECUTION_SUCCEEDED, ({ data }) => {
159
159
  });
160
160
  ```
161
161
 
162
+ ## Buy with card — a custom fiat onramp screen
163
+
164
+ `useOnramp` is the no-UI equivalent of
165
+ `beginDeposit({ initialScreen: 'card' })`: provider quotes + a hosted
166
+ checkout URL + the same settlement watching as a transfer. You render the
167
+ amount input, the provider list, and the progress UI.
168
+
169
+ ```tsx
170
+ import { useOnramp, useFiatCurrencies } from '@unifold/headless-react';
171
+
172
+ function BuyUSDC({ externalUserId }: Props) {
173
+ const [amount, setAmount] = useState('100');
174
+ const { data: fiat } = useFiatCurrencies(); // min/max limits, suggested amounts
175
+
176
+ const buy = useOnramp({
177
+ externalUserId,
178
+ // Payer country is auto-detected from IP; pass `countryCode` to override.
179
+ sourceAmount: amount, // live: changes refetch quotes (debounced 500ms)
180
+ destination: {
181
+ chainType: 'ethereum',
182
+ chainId: '8453',
183
+ tokenAddress: USDC_BASE,
184
+ recipientAddress: userTreasuryAddress,
185
+ },
186
+ onSuccess: (execution) => toast.success(`Received $${execution.destinationAmountUsd}`),
187
+ });
188
+
189
+ return (
190
+ <div>
191
+ <AmountInput value={amount} onChange={setAmount} />
192
+ {/* canSelectProvider is false under fiat-onramp smart routing (the
193
+ backend routes to a single provider) or when only one provider
194
+ quotes — hide the picker then, like the modal does. */}
195
+ {buy.canSelectProvider && (
196
+ <ProviderList
197
+ quotes={buy.quotes}
198
+ selected={buy.selectedQuote}
199
+ onSelect={(q) => buy.selectQuote(q.serviceProvider)}
200
+ />
201
+ )}
202
+ <button
203
+ disabled={buy.status !== 'ready' || !buy.selectedQuote}
204
+ onClick={() => {
205
+ // Synchronous URL build — safe to open inside the click handler.
206
+ const checkout = buy.createCheckout();
207
+ if (checkout) window.open(checkout.url, '_blank');
208
+ }}
209
+ >
210
+ Continue
211
+ </button>
212
+ {buy.status === 'awaiting_payment' && <WaitingForProvider checkout={buy.checkout!} />}
213
+ {buy.status === 'processing' && <MyProgress execution={buy.latestExecution!} />}
214
+ </div>
215
+ );
216
+ }
217
+ ```
218
+
219
+ The lifecycle state machine:
220
+
221
+ ```
222
+ idle → preparing → quoting → ready → awaiting_payment ⇄ processing
223
+ ↘ error (fatal: addresses / no onramp route / invalid recipient)
224
+ ```
225
+
226
+ A full reference screen — amount modes, currency picker, provider list,
227
+ checkout redirect, settlement — lives in `apps/ui-demo/app/onramp/headless`
228
+ (`/demo/onramp/headless`), and `docs/recipes/deposits/headless-card-onramp.mdx`
229
+ walks through building the flow step by step (amount input and prefills,
230
+ quoting, checkout, polling, crediting, error handling).
231
+
232
+ Same outcome philosophy as `useDeposit`: **outcomes never appear on the
233
+ session status** — read them from `executions`/`latestExecution`, the
234
+ `direct_execution.succeeded`/`.failed` events, or `session.waitForSuccess()`.
235
+ Under the hood the settlement phase _is_ a `DepositSession` (method `'card'`)
236
+ composed over the same deposit addresses, so detection polling, the scan
237
+ nudge, and the lookback window behave identically across rails.
238
+
239
+ Worth knowing:
240
+
241
+ - **Quotes auto-refresh every 60s** until checkout (configure with
242
+ `quoteRefreshIntervalMs`, 0 disables); `quotesUpdatedAt` drives your own
243
+ countdown. `selectQuote()` is sticky across refreshes and falls back to the
244
+ backend's top quote if the provider stops quoting.
245
+ - **Provider choice has three modes.** Default: the backend's top quote
246
+ (`quotes[0]` — the order encodes provider priority) is auto-selected. Host
247
+ choice: render `quotes` and call `selectQuote()`, or pass a one-shot
248
+ `createCheckout({ serviceProvider })` without touching the sticky
249
+ selection. Backend choice: projects with **fiat-onramp smart routing**
250
+ enabled get exactly one routed quote — `canSelectProvider` is `false` and
251
+ there is nothing to pick.
252
+ - **Quote failures are non-fatal** (`error.code === 'QUOTES_FAILED'`) — quotes
253
+ are cleared rather than left stale, and refresh keeps trying.
254
+ - **Payer country is auto-detected** from the user's IP when `countryCode`
255
+ is omitted ('US' fallback if detection fails — modal parity); the
256
+ effective value is exposed as `countryCode` on the result. Pass
257
+ `countryCode` (live) to override with your own geo signal. This is the
258
+ one deliberate exception to the headless SDK's not-IP-aware stance:
259
+ for the onramp, country is a functional input of quoting, not a policy
260
+ gate. Changing it live re-resolves the onramp route as well as the quotes
261
+ (routing is geo-dependent), so `destinationToken` and `quotes` are briefly
262
+ empty while that settles.
263
+ - **Amount validation is host-side**: check `minimum_amount`/`maximum_amount`
264
+ from `useFiatCurrencies` before quoting; the session only requires a
265
+ parseable amount > 0.
266
+ - **Two amount modes — provide exactly one.** `sourceAmount` ("spend 100
267
+ USD", fees included; `quote.destinationAmount` is what you receive) or
268
+ `destinationAmount` ("receive exactly 100 USDC", provider fees added on
269
+ top; `quote.sourceAmount` is the fiat the user pays). Both are live inputs;
270
+ swapping which one you pass switches modes in place. In destination mode
271
+ only providers that support fixed-destination quoting appear in `quotes` —
272
+ the backend filters the rest — and the destination token has to be a
273
+ stablecoin (`destinationToken.isStablecoin`), because the amount prices the
274
+ provider-side currency. For anything else, quoting stops with a non-fatal
275
+ `DESTINATION_AMOUNT_UNSUPPORTED` error until you switch back to
276
+ `sourceAmount` (the modal shows an error screen for the same case).
277
+ - `onramp_session.created` and `direct_execution.*` events are byte-compatible
278
+ with the modal SDK's `onEvent`, so one handler can serve both surfaces.
279
+ - Other rails ride the same hook via `paymentMethodType`
280
+ (`'card' | 'apple_pay' | 'sepa' | 'us_bank_account'`; default `'card'`).
281
+
162
282
  ### Vanilla JS (no React)
163
283
 
164
- The controller is usable without React from `@unifold/core`:
284
+ The controllers are usable without React from `@unifold/core`:
165
285
 
166
286
  ```ts
167
287
  import { createUnifoldClient } from '@unifold/core';
@@ -177,24 +297,46 @@ showSuccess(execution);
177
297
  session.destroy();
178
298
  ```
179
299
 
300
+ The onramp follows the same shape:
301
+
302
+ ```ts
303
+ const card = unifold.createOnrampSession({
304
+ externalUserId,
305
+ destination,
306
+ quoteRequest: { sourceAmount: '100' }, // country auto-detected; override with countryCode
307
+ });
308
+ await card.start(); // addresses + onramp route + first quotes
309
+ const checkout = card.createCheckout(); // sync — build URL from the selected quote
310
+ openUrl(checkout.url);
311
+ const execution = await card.waitForSuccess();
312
+ card.destroy();
313
+ ```
314
+
180
315
  ## Hooks
181
316
 
182
- | Hook | Purpose |
183
- | --- | --- |
184
- | `useDeposit` | Flagship flow hook: deposit addresses + execution detection + status state machine + events |
185
- | `useDepositAddresses` | Addresses without a live session (cached, idempotent create) |
186
- | `useSupportedDepositTokens` | Source token/chain list for custom pickers |
187
- | `useExecutions` | Deposit history (tracker screens) |
188
- | `useAllowedCountry` | Geo gate the modal uses; decide what to render when blocked |
189
- | `useAddressValidation` | Inline recipient validation (e.g. Algorand opt-in) |
190
- | `useUnifoldClient` | Escape hatch to the configured vanilla client |
317
+ | Hook | Purpose |
318
+ | --------------------------- | ------------------------------------------------------------------------------------------- |
319
+ | `useDeposit` | Flagship flow hook: deposit addresses + execution detection + status state machine + events |
320
+ | `useOnramp` | Card onramp flow hook: quotes + provider checkout URL + settlement watching |
321
+ | `useDepositAddresses` | Addresses without a live session (cached, idempotent create) |
322
+ | `useSupportedDepositTokens` | Source token/chain list for custom pickers |
323
+ | `useFiatCurrencies` | Fiat currencies + amount limits/suggestions for card onramp screens |
324
+ | `useExecutions` | Deposit history (tracker screens) |
325
+ | `useAllowedCountry` | Geo gate the modal uses; decide what to render when blocked |
326
+ | `useAddressValidation` | Inline recipient validation (e.g. Algorand opt-in) |
327
+ | `useUnifoldClient` | Escape hatch to the configured vanilla client |
191
328
 
192
329
  ## Events
193
330
 
194
331
  `resource.action` names with webhook-mirroring envelopes; `direct_execution.succeeded`
195
332
  is byte-compatible with the modal SDK's `onEvent`:
196
333
 
334
+ Envelope fields mirror the webhook payload, so `created` is a Unix timestamp in
335
+ **seconds** (multiply by 1000 before handing it to `Date`).
336
+
197
337
  - `deposit_session.started` / `.addresses_created` / `.confirmation_started` / `.stopped` / `.errored`
338
+ - `onramp_session.started` / `.addresses_created` / `.quotes_updated` / `.created` / `.stopped` / `.errored`
339
+ (`onramp_session.created` = checkout built — same name and `externalId` payload as the modal)
198
340
  - `direct_execution.detected` / `.updated` / `.succeeded` / `.failed`
199
341
 
200
342
  When mixing the modal and headless surfaces, dedupe on `execution.id` (envelope
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { UnifoldConfig, UnifoldProvider, UnifoldProviderProps, useUnifold } from '@unifold/react-provider';
2
- import { UnifoldClient, ChainType, EvmContractCall, DepositMethod, DepositSessionEvent, DepositAddress, DirectExecution, ExecutionStatus, DepositSessionError, DepositSessionStatus, DepositSession, ActionType, ProductType, SupportedToken, AddressValidationFailureCode } from '@unifold/core';
3
- export { ActionType, ChainType, DepositAddress, DepositMethod, DepositSession, DepositSessionDestination, DepositSessionError, DepositSessionErrorCode, DepositSessionEvent, DepositSessionEventMap, DepositSessionEventType, DepositSessionParams, DepositSessionSnapshot, DepositSessionStatus, DepositSessionWaitError, DepositSessionWaitErrorCode, DepositSessionWaitOptions, DirectExecution, EvmContractCall, ExecutionStatus, SupportedChain, SupportedToken, UnifoldClient, UnifoldClientOptions, createUnifoldClient } from '@unifold/core';
2
+ import { UnifoldClient, ChainType, EvmContractCall, DepositMethod, DepositSessionEvent, DepositAddress, DirectExecution, ExecutionStatus, DepositSessionError, DepositSessionStatus, DepositSession, OnrampSessionPaymentMethodType, OnrampSessionEvent, OnrampProviderQuote, OnrampSessionError, OnrampSessionStatus, OnrampDestinationToken, OnrampCheckout, OnrampQuoteRequest, OnrampSession, ActionType, ProductType, SupportedToken, AddressValidationFailureCode, FiatCurrenciesResponse } from '@unifold/core';
3
+ export { ActionType, ChainType, DepositAddress, DepositMethod, DepositSession, DepositSessionDestination, DepositSessionError, DepositSessionErrorCode, DepositSessionEvent, DepositSessionEventMap, DepositSessionEventType, DepositSessionParams, DepositSessionSnapshot, DepositSessionStatus, DepositSessionWaitError, DepositSessionWaitErrorCode, DepositSessionWaitOptions, DirectExecution, EvmContractCall, ExecutionStatus, FiatCurrency, OnrampCheckout, OnrampDestinationToken, OnrampProviderQuote, OnrampQuoteRequest, OnrampSession, OnrampSessionError, OnrampSessionErrorCode, OnrampSessionEvent, OnrampSessionEventMap, OnrampSessionEventType, OnrampSessionParams, OnrampSessionPaymentMethodType, OnrampSessionSnapshot, OnrampSessionStatus, OnrampSessionWaitError, OnrampSessionWaitErrorCode, OnrampSessionWaitOptions, SupportedChain, SupportedToken, UnifoldClient, UnifoldClientOptions, createUnifoldClient } from '@unifold/core';
4
4
  import * as _tanstack_react_query from '@tanstack/react-query';
5
5
 
6
6
  /**
@@ -98,6 +98,170 @@ interface UseDepositResult {
98
98
  */
99
99
  declare function useDeposit(options: UseDepositOptions): UseDepositResult;
100
100
 
101
+ interface UseOnrampOptions {
102
+ /** Host platform's stable user identifier (maps to external_user_id). */
103
+ externalUserId: string | undefined;
104
+ /** Destination — what the purchase converts into and where it lands. */
105
+ destination: UseDepositDestination;
106
+ /**
107
+ * ISO 3166-1 alpha-2 country of the payer. OPTIONAL — when omitted, the
108
+ * session auto-detects it from the user's IP during start ('US' fallback,
109
+ * modal parity). Provide it only to override detection with your own geo
110
+ * signal; live like the other quote inputs. The effective value is
111
+ * exposed as `countryCode` on the result.
112
+ */
113
+ countryCode?: string;
114
+ /** ISO 3166-2 subdivision (e.g. 'US-NY') when relevant for provider routing. */
115
+ subdivisionCode?: string;
116
+ /**
117
+ * Fiat amount to spend (fees included), as a decimal string. Live: changes
118
+ * refetch quotes (debounced). Provide exactly one of `sourceAmount` /
119
+ * `destinationAmount` — the amount mode.
120
+ */
121
+ sourceAmount?: string;
122
+ /**
123
+ * Fixed crypto (destination) amount to receive, with provider fees added
124
+ * on top — "receive 100 USDC". Live like `sourceAmount`, and mutually
125
+ * exclusive with it. Only providers that support fixed-destination quoting
126
+ * appear in `quotes`; each quote's `sourceAmount` is the fiat the user
127
+ * pays.
128
+ *
129
+ * Requires a stablecoin destination (`destinationToken.isStablecoin`) —
130
+ * the amount prices the provider-side currency, so it only means "receive
131
+ * 100 of the requested token" for stablecoins. Otherwise quoting stops
132
+ * with a non-fatal `DESTINATION_AMOUNT_UNSUPPORTED` error.
133
+ */
134
+ destinationAmount?: string;
135
+ /** ISO 4217 fiat currency code the user pays in. Live like `sourceAmount`. @default 'usd' */
136
+ sourceCurrency?: string;
137
+ /** Payment rail forwarded to the provider session. @default 'card' */
138
+ paymentMethodType?: OnrampSessionPaymentMethodType;
139
+ /** Prefill email forwarded to the provider checkout. */
140
+ email?: string;
141
+ /** Quote auto-refresh interval; 0 disables. @default 60000 */
142
+ quoteRefreshIntervalMs?: number;
143
+ /**
144
+ * Debounce applied when the live quote inputs (amount/currency/country)
145
+ * change between renders, so binding an input's value doesn't hit the
146
+ * quotes API per keystroke (parity with the modal's 500ms). @default 500
147
+ */
148
+ quoteDebounceMs?: number;
149
+ /**
150
+ * When true (default), start() is called automatically once externalUserId
151
+ * and the provider's publishable key are present. Set false to gate on
152
+ * your own UI.
153
+ */
154
+ autoStart?: boolean;
155
+ /** Every lifecycle event ('*' subscription) — analytics/debug fan-out. */
156
+ onEvent?: (event: OnrampSessionEvent) => void;
157
+ /** onramp_session.addresses_created */
158
+ onAddressesReady?: (addresses: DepositAddress[]) => void;
159
+ /** onramp_session.quotes_updated */
160
+ onQuotes?: (quotes: OnrampProviderQuote[], selectedQuote: OnrampProviderQuote | null) => void;
161
+ /** onramp_session.created (checkout built; host opens `url`) */
162
+ onCheckoutCreated?: (checkout: {
163
+ externalId: string;
164
+ url: string;
165
+ serviceProvider: string;
166
+ }) => void;
167
+ /** direct_execution.detected */
168
+ onExecutionDetected?: (execution: DirectExecution) => void;
169
+ /** direct_execution.updated */
170
+ onExecutionUpdated?: (execution: DirectExecution & {
171
+ previousStatus: ExecutionStatus | null;
172
+ }) => void;
173
+ /** direct_execution.succeeded */
174
+ onSuccess?: (execution: DirectExecution) => void;
175
+ /** direct_execution.failed + onramp_session.errored */
176
+ onError?: (error: OnrampSessionError) => void;
177
+ /** Fired whenever snapshot.status changes. */
178
+ onStatusChange?: (status: OnrampSessionStatus) => void;
179
+ }
180
+ interface UseOnrampResult {
181
+ status: OnrampSessionStatus;
182
+ /**
183
+ * Effective payer country driving quotes (host override or IP-detected;
184
+ * null until known).
185
+ */
186
+ countryCode: string | null;
187
+ /** Deposit addresses backing the purchase; empty until created. */
188
+ addresses: DepositAddress[];
189
+ /**
190
+ * Provider-side network/currency the fiat converts on; null until resolved,
191
+ * and briefly again while a `countryCode` change re-resolves the route.
192
+ */
193
+ destinationToken: OnrampDestinationToken | null;
194
+ /** Latest provider quotes, backend priority order. */
195
+ quotes: OnrampProviderQuote[];
196
+ /** Quote used by createCheckout() — quotes[0] unless selectQuote() was called. */
197
+ selectedQuote: OnrampProviderQuote | null;
198
+ /** False once the host picked a provider via selectQuote(). */
199
+ isQuoteAutoSelected: boolean;
200
+ /**
201
+ * True when there is more than one quote to pick between — gate your
202
+ * provider-picker UI on this. False under the project's fiat-onramp smart
203
+ * routing (backend collapses the list to one routed quote).
204
+ */
205
+ canSelectProvider: boolean;
206
+ isRefreshingQuotes: boolean;
207
+ /** Epoch ms of the last successful quote fetch — drive your own countdown. */
208
+ quotesUpdatedAt: number | null;
209
+ /** The active provider checkout; null until createCheckout(). */
210
+ checkout: OnrampCheckout | null;
211
+ /** All executions observed since checkout, newest first. */
212
+ executions: DirectExecution[];
213
+ latestExecution: DirectExecution | null;
214
+ /** True while the backend is actively scanning for the provider's transfer. */
215
+ isCheckingDeposit: boolean;
216
+ error: OnrampSessionError | null;
217
+ /** Start the session (relevant when autoStart: false, or to retry after error). */
218
+ start: () => Promise<void>;
219
+ /** Refetch quotes with the current request. */
220
+ refreshQuotes: () => Promise<void>;
221
+ /** Imperative alternative to the live sourceAmount/sourceCurrency options. */
222
+ updateQuoteRequest: (patch: Partial<OnrampQuoteRequest>) => void;
223
+ /** Pick a provider quote (sticky across refreshes). Returns it, or null when absent. */
224
+ selectQuote: (serviceProvider: string) => OnrampProviderQuote | null;
225
+ /**
226
+ * Build the provider checkout and start watching for settlement. Uses the
227
+ * selected quote, or a one-shot `options.serviceProvider` choice.
228
+ * Synchronous — call it in your click handler and `window.open(result.url)`
229
+ * to stay popup-blocker safe. Returns null while the session doesn't exist
230
+ * yet; throws when called before quotes are ready (gate on `status`).
231
+ */
232
+ createCheckout: (options?: {
233
+ serviceProvider?: string;
234
+ email?: string;
235
+ externalId?: string;
236
+ }) => OnrampCheckout | null;
237
+ /** Stop quote refresh and settlement watching. */
238
+ stop: () => void;
239
+ /** stop() + fresh run + start(). */
240
+ restart: () => Promise<void>;
241
+ /** Escape hatch to the underlying session (waiters, extra subscriptions). */
242
+ session: OnrampSession | null;
243
+ }
244
+ /**
245
+ * Headless fiat-onramp hook (buy with card by default) — the no-UI
246
+ * equivalent of `beginDeposit({ initialScreen: 'card' })`.
247
+ *
248
+ * Wraps a core {@link OnrampSession}: creates it, subscribes via
249
+ * `useSyncExternalStore`, destroys it on unmount, and exposes the snapshot,
250
+ * imperative controls, and callback sugar. The host renders 100% of the UI —
251
+ * amount input, provider list, checkout button, settlement progress.
252
+ *
253
+ * The quote inputs (`sourceAmount` / `destinationAmount` — provide exactly
254
+ * one — plus `sourceCurrency`, `countryCode`, `subdivisionCode`) are LIVE:
255
+ * changing them updates the running session (debounced) instead of tearing
256
+ * it down, so you can bind them straight to form state. Swapping which
257
+ * amount is provided switches the amount mode in place. Changing the flow
258
+ * identity (user, destination, payment method) creates a fresh session.
259
+ *
260
+ * SSR-safe: the session is constructed in an effect; server renders see
261
+ * `status: 'idle'` and empty arrays.
262
+ */
263
+ declare function useOnramp(options: UseOnrampOptions): UseOnrampResult;
264
+
101
265
  interface UseDepositAddressesOptions {
102
266
  /** Host platform's stable user identifier (maps to external_user_id). */
103
267
  externalUserId: string | undefined;
@@ -215,4 +379,20 @@ interface AddressValidationResult {
215
379
  */
216
380
  declare function useAddressValidation(options: UseAddressValidationOptions): AddressValidationResult;
217
381
 
218
- export { type AddressValidationResult, type AllowedCountryResult, type UseAddressValidationOptions, type UseDepositAddressesOptions, type UseDepositDestination, type UseDepositOptions, type UseDepositResult, type UseExecutionsOptions, type UseSupportedDepositTokensOptions, useAddressValidation, useAllowedCountry, useDeposit, useDepositAddresses, useExecutions, useSupportedDepositTokens, useUnifoldClient };
382
+ interface UseFiatCurrenciesOptions {
383
+ /** Whether the query should execute. Defaults to true. */
384
+ enabled?: boolean;
385
+ }
386
+ /**
387
+ * Fiat currencies supported by the card onramp — for building custom
388
+ * currency pickers and amount screens next to {@link useOnramp}.
389
+ *
390
+ * Each {@link FiatCurrency} carries `minimum_amount` / `maximum_amount`
391
+ * (validate the user's amount host-side before quoting — the session only
392
+ * requires a parseable amount > 0), `default_amount`, `suggested_amounts`
393
+ * (quick-pick buttons), and `country_codes` (map your geo signal to a
394
+ * default currency). `data.preferred` lists currency codes to pin first.
395
+ */
396
+ declare function useFiatCurrencies(options?: UseFiatCurrenciesOptions): _tanstack_react_query.UseQueryResult<FiatCurrenciesResponse, Error>;
397
+
398
+ export { type AddressValidationResult, type AllowedCountryResult, type UseAddressValidationOptions, type UseDepositAddressesOptions, type UseDepositDestination, type UseDepositOptions, type UseDepositResult, type UseExecutionsOptions, type UseFiatCurrenciesOptions, type UseOnrampOptions, type UseOnrampResult, type UseSupportedDepositTokensOptions, useAddressValidation, useAllowedCountry, useDeposit, useDepositAddresses, useExecutions, useFiatCurrencies, useOnramp, useSupportedDepositTokens, useUnifoldClient };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { UnifoldConfig, UnifoldProvider, UnifoldProviderProps, useUnifold } from '@unifold/react-provider';
2
- import { UnifoldClient, ChainType, EvmContractCall, DepositMethod, DepositSessionEvent, DepositAddress, DirectExecution, ExecutionStatus, DepositSessionError, DepositSessionStatus, DepositSession, ActionType, ProductType, SupportedToken, AddressValidationFailureCode } from '@unifold/core';
3
- export { ActionType, ChainType, DepositAddress, DepositMethod, DepositSession, DepositSessionDestination, DepositSessionError, DepositSessionErrorCode, DepositSessionEvent, DepositSessionEventMap, DepositSessionEventType, DepositSessionParams, DepositSessionSnapshot, DepositSessionStatus, DepositSessionWaitError, DepositSessionWaitErrorCode, DepositSessionWaitOptions, DirectExecution, EvmContractCall, ExecutionStatus, SupportedChain, SupportedToken, UnifoldClient, UnifoldClientOptions, createUnifoldClient } from '@unifold/core';
2
+ import { UnifoldClient, ChainType, EvmContractCall, DepositMethod, DepositSessionEvent, DepositAddress, DirectExecution, ExecutionStatus, DepositSessionError, DepositSessionStatus, DepositSession, OnrampSessionPaymentMethodType, OnrampSessionEvent, OnrampProviderQuote, OnrampSessionError, OnrampSessionStatus, OnrampDestinationToken, OnrampCheckout, OnrampQuoteRequest, OnrampSession, ActionType, ProductType, SupportedToken, AddressValidationFailureCode, FiatCurrenciesResponse } from '@unifold/core';
3
+ export { ActionType, ChainType, DepositAddress, DepositMethod, DepositSession, DepositSessionDestination, DepositSessionError, DepositSessionErrorCode, DepositSessionEvent, DepositSessionEventMap, DepositSessionEventType, DepositSessionParams, DepositSessionSnapshot, DepositSessionStatus, DepositSessionWaitError, DepositSessionWaitErrorCode, DepositSessionWaitOptions, DirectExecution, EvmContractCall, ExecutionStatus, FiatCurrency, OnrampCheckout, OnrampDestinationToken, OnrampProviderQuote, OnrampQuoteRequest, OnrampSession, OnrampSessionError, OnrampSessionErrorCode, OnrampSessionEvent, OnrampSessionEventMap, OnrampSessionEventType, OnrampSessionParams, OnrampSessionPaymentMethodType, OnrampSessionSnapshot, OnrampSessionStatus, OnrampSessionWaitError, OnrampSessionWaitErrorCode, OnrampSessionWaitOptions, SupportedChain, SupportedToken, UnifoldClient, UnifoldClientOptions, createUnifoldClient } from '@unifold/core';
4
4
  import * as _tanstack_react_query from '@tanstack/react-query';
5
5
 
6
6
  /**
@@ -98,6 +98,170 @@ interface UseDepositResult {
98
98
  */
99
99
  declare function useDeposit(options: UseDepositOptions): UseDepositResult;
100
100
 
101
+ interface UseOnrampOptions {
102
+ /** Host platform's stable user identifier (maps to external_user_id). */
103
+ externalUserId: string | undefined;
104
+ /** Destination — what the purchase converts into and where it lands. */
105
+ destination: UseDepositDestination;
106
+ /**
107
+ * ISO 3166-1 alpha-2 country of the payer. OPTIONAL — when omitted, the
108
+ * session auto-detects it from the user's IP during start ('US' fallback,
109
+ * modal parity). Provide it only to override detection with your own geo
110
+ * signal; live like the other quote inputs. The effective value is
111
+ * exposed as `countryCode` on the result.
112
+ */
113
+ countryCode?: string;
114
+ /** ISO 3166-2 subdivision (e.g. 'US-NY') when relevant for provider routing. */
115
+ subdivisionCode?: string;
116
+ /**
117
+ * Fiat amount to spend (fees included), as a decimal string. Live: changes
118
+ * refetch quotes (debounced). Provide exactly one of `sourceAmount` /
119
+ * `destinationAmount` — the amount mode.
120
+ */
121
+ sourceAmount?: string;
122
+ /**
123
+ * Fixed crypto (destination) amount to receive, with provider fees added
124
+ * on top — "receive 100 USDC". Live like `sourceAmount`, and mutually
125
+ * exclusive with it. Only providers that support fixed-destination quoting
126
+ * appear in `quotes`; each quote's `sourceAmount` is the fiat the user
127
+ * pays.
128
+ *
129
+ * Requires a stablecoin destination (`destinationToken.isStablecoin`) —
130
+ * the amount prices the provider-side currency, so it only means "receive
131
+ * 100 of the requested token" for stablecoins. Otherwise quoting stops
132
+ * with a non-fatal `DESTINATION_AMOUNT_UNSUPPORTED` error.
133
+ */
134
+ destinationAmount?: string;
135
+ /** ISO 4217 fiat currency code the user pays in. Live like `sourceAmount`. @default 'usd' */
136
+ sourceCurrency?: string;
137
+ /** Payment rail forwarded to the provider session. @default 'card' */
138
+ paymentMethodType?: OnrampSessionPaymentMethodType;
139
+ /** Prefill email forwarded to the provider checkout. */
140
+ email?: string;
141
+ /** Quote auto-refresh interval; 0 disables. @default 60000 */
142
+ quoteRefreshIntervalMs?: number;
143
+ /**
144
+ * Debounce applied when the live quote inputs (amount/currency/country)
145
+ * change between renders, so binding an input's value doesn't hit the
146
+ * quotes API per keystroke (parity with the modal's 500ms). @default 500
147
+ */
148
+ quoteDebounceMs?: number;
149
+ /**
150
+ * When true (default), start() is called automatically once externalUserId
151
+ * and the provider's publishable key are present. Set false to gate on
152
+ * your own UI.
153
+ */
154
+ autoStart?: boolean;
155
+ /** Every lifecycle event ('*' subscription) — analytics/debug fan-out. */
156
+ onEvent?: (event: OnrampSessionEvent) => void;
157
+ /** onramp_session.addresses_created */
158
+ onAddressesReady?: (addresses: DepositAddress[]) => void;
159
+ /** onramp_session.quotes_updated */
160
+ onQuotes?: (quotes: OnrampProviderQuote[], selectedQuote: OnrampProviderQuote | null) => void;
161
+ /** onramp_session.created (checkout built; host opens `url`) */
162
+ onCheckoutCreated?: (checkout: {
163
+ externalId: string;
164
+ url: string;
165
+ serviceProvider: string;
166
+ }) => void;
167
+ /** direct_execution.detected */
168
+ onExecutionDetected?: (execution: DirectExecution) => void;
169
+ /** direct_execution.updated */
170
+ onExecutionUpdated?: (execution: DirectExecution & {
171
+ previousStatus: ExecutionStatus | null;
172
+ }) => void;
173
+ /** direct_execution.succeeded */
174
+ onSuccess?: (execution: DirectExecution) => void;
175
+ /** direct_execution.failed + onramp_session.errored */
176
+ onError?: (error: OnrampSessionError) => void;
177
+ /** Fired whenever snapshot.status changes. */
178
+ onStatusChange?: (status: OnrampSessionStatus) => void;
179
+ }
180
+ interface UseOnrampResult {
181
+ status: OnrampSessionStatus;
182
+ /**
183
+ * Effective payer country driving quotes (host override or IP-detected;
184
+ * null until known).
185
+ */
186
+ countryCode: string | null;
187
+ /** Deposit addresses backing the purchase; empty until created. */
188
+ addresses: DepositAddress[];
189
+ /**
190
+ * Provider-side network/currency the fiat converts on; null until resolved,
191
+ * and briefly again while a `countryCode` change re-resolves the route.
192
+ */
193
+ destinationToken: OnrampDestinationToken | null;
194
+ /** Latest provider quotes, backend priority order. */
195
+ quotes: OnrampProviderQuote[];
196
+ /** Quote used by createCheckout() — quotes[0] unless selectQuote() was called. */
197
+ selectedQuote: OnrampProviderQuote | null;
198
+ /** False once the host picked a provider via selectQuote(). */
199
+ isQuoteAutoSelected: boolean;
200
+ /**
201
+ * True when there is more than one quote to pick between — gate your
202
+ * provider-picker UI on this. False under the project's fiat-onramp smart
203
+ * routing (backend collapses the list to one routed quote).
204
+ */
205
+ canSelectProvider: boolean;
206
+ isRefreshingQuotes: boolean;
207
+ /** Epoch ms of the last successful quote fetch — drive your own countdown. */
208
+ quotesUpdatedAt: number | null;
209
+ /** The active provider checkout; null until createCheckout(). */
210
+ checkout: OnrampCheckout | null;
211
+ /** All executions observed since checkout, newest first. */
212
+ executions: DirectExecution[];
213
+ latestExecution: DirectExecution | null;
214
+ /** True while the backend is actively scanning for the provider's transfer. */
215
+ isCheckingDeposit: boolean;
216
+ error: OnrampSessionError | null;
217
+ /** Start the session (relevant when autoStart: false, or to retry after error). */
218
+ start: () => Promise<void>;
219
+ /** Refetch quotes with the current request. */
220
+ refreshQuotes: () => Promise<void>;
221
+ /** Imperative alternative to the live sourceAmount/sourceCurrency options. */
222
+ updateQuoteRequest: (patch: Partial<OnrampQuoteRequest>) => void;
223
+ /** Pick a provider quote (sticky across refreshes). Returns it, or null when absent. */
224
+ selectQuote: (serviceProvider: string) => OnrampProviderQuote | null;
225
+ /**
226
+ * Build the provider checkout and start watching for settlement. Uses the
227
+ * selected quote, or a one-shot `options.serviceProvider` choice.
228
+ * Synchronous — call it in your click handler and `window.open(result.url)`
229
+ * to stay popup-blocker safe. Returns null while the session doesn't exist
230
+ * yet; throws when called before quotes are ready (gate on `status`).
231
+ */
232
+ createCheckout: (options?: {
233
+ serviceProvider?: string;
234
+ email?: string;
235
+ externalId?: string;
236
+ }) => OnrampCheckout | null;
237
+ /** Stop quote refresh and settlement watching. */
238
+ stop: () => void;
239
+ /** stop() + fresh run + start(). */
240
+ restart: () => Promise<void>;
241
+ /** Escape hatch to the underlying session (waiters, extra subscriptions). */
242
+ session: OnrampSession | null;
243
+ }
244
+ /**
245
+ * Headless fiat-onramp hook (buy with card by default) — the no-UI
246
+ * equivalent of `beginDeposit({ initialScreen: 'card' })`.
247
+ *
248
+ * Wraps a core {@link OnrampSession}: creates it, subscribes via
249
+ * `useSyncExternalStore`, destroys it on unmount, and exposes the snapshot,
250
+ * imperative controls, and callback sugar. The host renders 100% of the UI —
251
+ * amount input, provider list, checkout button, settlement progress.
252
+ *
253
+ * The quote inputs (`sourceAmount` / `destinationAmount` — provide exactly
254
+ * one — plus `sourceCurrency`, `countryCode`, `subdivisionCode`) are LIVE:
255
+ * changing them updates the running session (debounced) instead of tearing
256
+ * it down, so you can bind them straight to form state. Swapping which
257
+ * amount is provided switches the amount mode in place. Changing the flow
258
+ * identity (user, destination, payment method) creates a fresh session.
259
+ *
260
+ * SSR-safe: the session is constructed in an effect; server renders see
261
+ * `status: 'idle'` and empty arrays.
262
+ */
263
+ declare function useOnramp(options: UseOnrampOptions): UseOnrampResult;
264
+
101
265
  interface UseDepositAddressesOptions {
102
266
  /** Host platform's stable user identifier (maps to external_user_id). */
103
267
  externalUserId: string | undefined;
@@ -215,4 +379,20 @@ interface AddressValidationResult {
215
379
  */
216
380
  declare function useAddressValidation(options: UseAddressValidationOptions): AddressValidationResult;
217
381
 
218
- export { type AddressValidationResult, type AllowedCountryResult, type UseAddressValidationOptions, type UseDepositAddressesOptions, type UseDepositDestination, type UseDepositOptions, type UseDepositResult, type UseExecutionsOptions, type UseSupportedDepositTokensOptions, useAddressValidation, useAllowedCountry, useDeposit, useDepositAddresses, useExecutions, useSupportedDepositTokens, useUnifoldClient };
382
+ interface UseFiatCurrenciesOptions {
383
+ /** Whether the query should execute. Defaults to true. */
384
+ enabled?: boolean;
385
+ }
386
+ /**
387
+ * Fiat currencies supported by the card onramp — for building custom
388
+ * currency pickers and amount screens next to {@link useOnramp}.
389
+ *
390
+ * Each {@link FiatCurrency} carries `minimum_amount` / `maximum_amount`
391
+ * (validate the user's amount host-side before quoting — the session only
392
+ * requires a parseable amount > 0), `default_amount`, `suggested_amounts`
393
+ * (quick-pick buttons), and `country_codes` (map your geo signal to a
394
+ * default currency). `data.preferred` lists currency codes to pin first.
395
+ */
396
+ declare function useFiatCurrencies(options?: UseFiatCurrenciesOptions): _tanstack_react_query.UseQueryResult<FiatCurrenciesResponse, Error>;
397
+
398
+ export { type AddressValidationResult, type AllowedCountryResult, type UseAddressValidationOptions, type UseDepositAddressesOptions, type UseDepositDestination, type UseDepositOptions, type UseDepositResult, type UseExecutionsOptions, type UseFiatCurrenciesOptions, type UseOnrampOptions, type UseOnrampResult, type UseSupportedDepositTokensOptions, useAddressValidation, useAllowedCountry, useDeposit, useDepositAddresses, useExecutions, useFiatCurrencies, useOnramp, useSupportedDepositTokens, useUnifoldClient };