@unifold/headless-react 0.1.70-beta.1
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 +198 -0
- package/dist/index.d.mts +218 -0
- package/dist/index.d.ts +218 -0
- package/dist/index.js +420 -0
- package/dist/index.mjs +402 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# @unifold/headless-react
|
|
2
|
+
|
|
3
|
+
Hooks-only (no UI) React SDK for Unifold crypto deposits. You own 100% of the
|
|
4
|
+
rendering; the SDK owns the data flow, session lifecycle, polling, and events.
|
|
5
|
+
|
|
6
|
+
- **Zero UI, zero CSS** — no components, no Tailwind, no portals.
|
|
7
|
+
- **Layered like Stripe.js** — the flow logic lives in a framework-agnostic
|
|
8
|
+
`DepositSession` controller in `@unifold/core`; this package is a thin React
|
|
9
|
+
binding over it.
|
|
10
|
+
- **Events first** — every state transition is observable as a typed event
|
|
11
|
+
using the same webhook-style envelope (`{ id, type, created, data: { object } }`)
|
|
12
|
+
as `@unifold/connect-react`'s `onEvent`. Callbacks (`onSuccess`, …) are sugar
|
|
13
|
+
over the event stream.
|
|
14
|
+
|
|
15
|
+
> Already using `@unifold/connect-react` (the modal SDK)? Don't install this
|
|
16
|
+
> package — import the same hooks from `@unifold/connect-react/headless`
|
|
17
|
+
> instead. Install exactly one of the two packages.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @unifold/headless-react
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @unifold/headless-react
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
Same provider as the modal SDK — the two SDKs can coexist in one app, sharing
|
|
30
|
+
the provider and QueryClient:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { UnifoldProvider } from '@unifold/headless-react';
|
|
34
|
+
|
|
35
|
+
function App() {
|
|
36
|
+
return (
|
|
37
|
+
<UnifoldProvider publishableKey="pk_live_...">
|
|
38
|
+
<YourApp />
|
|
39
|
+
</UnifoldProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quickstart — a custom deposit screen
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { useDeposit } from '@unifold/headless-react';
|
|
48
|
+
|
|
49
|
+
function DepositUSDC({ externalUserId }: { externalUserId: string }) {
|
|
50
|
+
const { status, getAddress, latestExecution } = useDeposit({
|
|
51
|
+
externalUserId,
|
|
52
|
+
destination: {
|
|
53
|
+
chainType: 'ethereum',
|
|
54
|
+
chainId: '8453',
|
|
55
|
+
tokenAddress: USDC_BASE,
|
|
56
|
+
recipientAddress: userTreasuryAddress,
|
|
57
|
+
},
|
|
58
|
+
onSuccess: (execution) => toast.success(`Received $${execution.destinationAmountUsd}`),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const eth = getAddress({ chainType: 'ethereum' });
|
|
62
|
+
if (!eth) return <Spinner />;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
<MyQrCode value={eth.address} />
|
|
67
|
+
<CopyField value={eth.address} />
|
|
68
|
+
{status === 'processing' && <MyProgress execution={latestExecution!} />}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`useDeposit` drives an explicit status state machine:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
idle → creating_addresses → awaiting_funds → processing → succeeded | failed
|
|
78
|
+
↘ error (fatal: address creation / invalid recipient)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The headless SDK is deliberately **not IP-aware** — `useDeposit` never geo-gates
|
|
82
|
+
address creation. If you want the modal's region gate, opt in with
|
|
83
|
+
`useAllowedCountry` and gate your own UI on `isAllowed`.
|
|
84
|
+
|
|
85
|
+
### Manual confirmation + full event wiring
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const deposit = useDeposit({
|
|
89
|
+
externalUserId,
|
|
90
|
+
destination,
|
|
91
|
+
confirmationMode: 'manual',
|
|
92
|
+
onEvent: (event) => analytics.track(event.type, event),
|
|
93
|
+
onExecutionUpdated: (execution) => setTimeline((t) => [...t, execution]),
|
|
94
|
+
onError: (error) => {
|
|
95
|
+
if (error.code === 'POLLING_ERROR') showBanner('Connection hiccup — still watching');
|
|
96
|
+
else showFailure(error);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
<Button onClick={deposit.confirmFundsSent} disabled={deposit.isCheckingDeposit}>
|
|
101
|
+
I've made the transfer
|
|
102
|
+
</Button>;
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Promise waiters
|
|
106
|
+
|
|
107
|
+
For imperative flows, the session exposes `await`-style sugar over the event
|
|
108
|
+
stream (they only listen — neither starts nor stops the session):
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// Generic primitive over the status state machine:
|
|
112
|
+
await session.waitForStatus('processing'); // deposit detected
|
|
113
|
+
await session.waitForStatus(['succeeded', 'failed']); // terminal outcome
|
|
114
|
+
|
|
115
|
+
// The 90% case — mirrors beginDeposit()'s promise contract
|
|
116
|
+
// (resolve on success, reject on failure):
|
|
117
|
+
try {
|
|
118
|
+
const execution = await session.waitForSuccess();
|
|
119
|
+
creditUser(execution.destinationAmountUsd);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// DepositSessionWaitError: DEPOSIT_FAILED | SESSION_ERROR | ABORTED | DESTROYED
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Both accept `{ signal }` — an `AbortSignal` cancels the *wait*, never the
|
|
126
|
+
session (a deposit isn't cancelable: once the user sends funds, they arrive
|
|
127
|
+
whether or not anyone is awaiting). For a deadline, compose the platform
|
|
128
|
+
primitive — `waitForSuccess({ signal: AbortSignal.timeout(60_000) })` — and
|
|
129
|
+
treat it as "outcome still unknown", not failure: keep the session (and your
|
|
130
|
+
UI) watching. From `useDeposit`, reach the waiters via the `session` escape
|
|
131
|
+
hatch.
|
|
132
|
+
|
|
133
|
+
**One session, many executions.** Unlike quote-scoped models (e.g. Privy's,
|
|
134
|
+
where one address maps to one order), a Unifold session can observe multiple
|
|
135
|
+
executions — the user may send twice, or on two different chains, to the same
|
|
136
|
+
universal addresses. `waitForSuccess` is one-shot **first-completion**
|
|
137
|
+
detection; the session keeps polling afterwards, and every settlement fires
|
|
138
|
+
its own `direct_execution.succeeded` event.
|
|
139
|
+
|
|
140
|
+
**Live outcomes only.** The 60s lookback window exists to catch deposits sent
|
|
141
|
+
moments before the session started — it only admits executions still
|
|
142
|
+
*in-flight* at first sight (their settlement then fires live). An execution
|
|
143
|
+
that already settled before the session began is history and never re-fires
|
|
144
|
+
success/failure events, so reopening a deposit screen right after a success
|
|
145
|
+
cannot double-credit. Render history with `useExecutions` instead. To credit each deposit, subscribe
|
|
146
|
+
to events (dedupe on `execution.id`) or render `executions` from the hook:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
session.on(DepositSessionEventType.EXECUTION_SUCCEEDED, ({ data }) => {
|
|
150
|
+
creditUser(data.object); // fires once per settled execution
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Vanilla JS (no React)
|
|
155
|
+
|
|
156
|
+
The controller is usable without React from `@unifold/core`:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { createUnifoldClient } from '@unifold/core';
|
|
160
|
+
|
|
161
|
+
const unifold = createUnifoldClient({ publishableKey: 'pk_live_...' });
|
|
162
|
+
const session = unifold.createDepositSession({ externalUserId, destination });
|
|
163
|
+
|
|
164
|
+
await session.start();
|
|
165
|
+
renderQr(session.getSnapshot().addresses);
|
|
166
|
+
|
|
167
|
+
const execution = await session.waitForSuccess();
|
|
168
|
+
showSuccess(execution);
|
|
169
|
+
session.destroy();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Hooks
|
|
173
|
+
|
|
174
|
+
| Hook | Purpose |
|
|
175
|
+
| --- | --- |
|
|
176
|
+
| `useDeposit` | Flagship flow hook: deposit addresses + execution detection + status state machine + events |
|
|
177
|
+
| `useDepositAddresses` | Addresses without a live session (cached, idempotent create) |
|
|
178
|
+
| `useSupportedDepositTokens` | Source token/chain list for custom pickers |
|
|
179
|
+
| `useExecutions` | Deposit history (tracker screens) |
|
|
180
|
+
| `useAllowedCountry` | Geo gate the modal uses; decide what to render when blocked |
|
|
181
|
+
| `useAddressValidation` | Inline recipient validation (e.g. Algorand opt-in) |
|
|
182
|
+
| `useUnifoldClient` | Escape hatch to the configured vanilla client |
|
|
183
|
+
|
|
184
|
+
## Events
|
|
185
|
+
|
|
186
|
+
`resource.action` names with webhook-mirroring envelopes; `direct_execution.succeeded`
|
|
187
|
+
is byte-compatible with the modal SDK's `onEvent`:
|
|
188
|
+
|
|
189
|
+
- `deposit_session.started` / `.addresses_created` / `.confirmation_started` / `.stopped` / `.errored`
|
|
190
|
+
- `direct_execution.detected` / `.updated` / `.succeeded` / `.failed`
|
|
191
|
+
|
|
192
|
+
When mixing the modal and headless surfaces, dedupe on `execution.id` (envelope
|
|
193
|
+
`sevt_` ids are minted per emitter), and keep one active surface per flow at a
|
|
194
|
+
time.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
Apache-2.0
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
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';
|
|
4
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a memoized {@link UnifoldClient} configured with the publishable key
|
|
8
|
+
* from the surrounding `UnifoldProvider`.
|
|
9
|
+
*
|
|
10
|
+
* Escape hatch for imperative access to the headless API (resource calls,
|
|
11
|
+
* `createDepositSession`) outside the provided hooks.
|
|
12
|
+
*/
|
|
13
|
+
declare function useUnifoldClient(): UnifoldClient | null;
|
|
14
|
+
|
|
15
|
+
interface UseDepositDestination {
|
|
16
|
+
chainType: ChainType;
|
|
17
|
+
chainId: string;
|
|
18
|
+
tokenAddress: string;
|
|
19
|
+
/** Address that receives the deposited funds. */
|
|
20
|
+
recipientAddress: string;
|
|
21
|
+
/** EVM-only post-delivery calls; same constraints as DepositConfig.contractCalls. */
|
|
22
|
+
contractCalls?: EvmContractCall[];
|
|
23
|
+
}
|
|
24
|
+
interface UseDepositOptions {
|
|
25
|
+
/** Host platform's stable user identifier (maps to external_user_id). */
|
|
26
|
+
externalUserId: string | undefined;
|
|
27
|
+
/** Destination — what the deposit converts into and where it lands. */
|
|
28
|
+
destination: UseDepositDestination;
|
|
29
|
+
/**
|
|
30
|
+
* When the backend scan nudge is armed: 'auto' (default, after 5s) or
|
|
31
|
+
* 'manual' (host calls confirmFundsSent() from its own "I've sent it" button).
|
|
32
|
+
*/
|
|
33
|
+
confirmationMode?: 'auto' | 'manual';
|
|
34
|
+
/** Rail hint stamped onto emitted events' `method`. @default 'transfer' */
|
|
35
|
+
method?: DepositMethod;
|
|
36
|
+
/**
|
|
37
|
+
* When true (default), start() is called automatically once externalUserId and the
|
|
38
|
+
* provider's publishable key are present. Set false to gate on your own UI
|
|
39
|
+
* (e.g. only start when the user opens your deposit sheet).
|
|
40
|
+
*/
|
|
41
|
+
autoStart?: boolean;
|
|
42
|
+
/** Every lifecycle event ('*' subscription) — analytics/debug fan-out. */
|
|
43
|
+
onEvent?: (event: DepositSessionEvent) => void;
|
|
44
|
+
/** deposit_session.addresses_created */
|
|
45
|
+
onAddressesReady?: (addresses: DepositAddress[]) => void;
|
|
46
|
+
/** direct_execution.detected */
|
|
47
|
+
onExecutionDetected?: (execution: DirectExecution) => void;
|
|
48
|
+
/** direct_execution.updated */
|
|
49
|
+
onExecutionUpdated?: (execution: DirectExecution & {
|
|
50
|
+
previousStatus: ExecutionStatus | null;
|
|
51
|
+
}) => void;
|
|
52
|
+
/** direct_execution.succeeded */
|
|
53
|
+
onSuccess?: (execution: DirectExecution) => void;
|
|
54
|
+
/** direct_execution.failed + deposit_session.errored */
|
|
55
|
+
onError?: (error: DepositSessionError) => void;
|
|
56
|
+
/** Fired whenever snapshot.status changes. */
|
|
57
|
+
onStatusChange?: (status: DepositSessionStatus) => void;
|
|
58
|
+
}
|
|
59
|
+
interface UseDepositResult {
|
|
60
|
+
status: DepositSessionStatus;
|
|
61
|
+
/** Deposit addresses (one per chain type); empty until created. */
|
|
62
|
+
addresses: DepositAddress[];
|
|
63
|
+
/**
|
|
64
|
+
* Convenience: the deposit address matching the query. Takes an options
|
|
65
|
+
* object so future selectors (e.g. `addressType` when a chain type exposes
|
|
66
|
+
* multiple address formats) can be added without a breaking change.
|
|
67
|
+
*/
|
|
68
|
+
getAddress: (query: {
|
|
69
|
+
chainType: ChainType;
|
|
70
|
+
}) => DepositAddress | undefined;
|
|
71
|
+
/** All executions observed this session, newest first. */
|
|
72
|
+
executions: DirectExecution[];
|
|
73
|
+
latestExecution: DirectExecution | null;
|
|
74
|
+
/** True while the backend is actively checking for the deposit (scan nudge armed). */
|
|
75
|
+
isCheckingDeposit: boolean;
|
|
76
|
+
error: DepositSessionError | null;
|
|
77
|
+
/** Start the session (relevant when autoStart: false, or to retry after error). */
|
|
78
|
+
start: () => Promise<void>;
|
|
79
|
+
/** Arm the backend scan nudge in 'manual' mode ("I've sent it" button). */
|
|
80
|
+
confirmFundsSent: () => void;
|
|
81
|
+
/** Stop all polling. */
|
|
82
|
+
stop: () => void;
|
|
83
|
+
/** stop() + fresh baseline + start(). */
|
|
84
|
+
restart: () => Promise<void>;
|
|
85
|
+
/** Escape hatch to the underlying session (e.g. extra event subscriptions). */
|
|
86
|
+
session: DepositSession | null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Flagship headless deposit hook — the no-UI equivalent of
|
|
90
|
+
* `beginDeposit({ initialScreen: 'transfer' })`.
|
|
91
|
+
*
|
|
92
|
+
* Wraps a core {@link DepositSession}: creates it, subscribes via
|
|
93
|
+
* `useSyncExternalStore`, destroys it on unmount, and exposes the snapshot,
|
|
94
|
+
* imperative controls, and callback sugar. The host renders 100% of the UI.
|
|
95
|
+
*
|
|
96
|
+
* SSR-safe: the session is constructed in an effect; server renders see
|
|
97
|
+
* `status: 'idle'` and empty arrays.
|
|
98
|
+
*/
|
|
99
|
+
declare function useDeposit(options: UseDepositOptions): UseDepositResult;
|
|
100
|
+
|
|
101
|
+
interface UseDepositAddressesOptions {
|
|
102
|
+
/** Host platform's stable user identifier (maps to external_user_id). */
|
|
103
|
+
externalUserId: string | undefined;
|
|
104
|
+
destination?: {
|
|
105
|
+
chainType?: ChainType;
|
|
106
|
+
chainId?: string;
|
|
107
|
+
tokenAddress?: string;
|
|
108
|
+
recipientAddress?: string;
|
|
109
|
+
contractCalls?: EvmContractCall[];
|
|
110
|
+
};
|
|
111
|
+
/** Action type for the deposit address. @default server default ('deposit') */
|
|
112
|
+
actionType?: ActionType;
|
|
113
|
+
/** Whether the query should execute. Defaults to true. */
|
|
114
|
+
enabled?: boolean;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Fetch/create the user's deposit addresses with react-query caching.
|
|
118
|
+
*
|
|
119
|
+
* `POST /v1/public/deposit_addresses` is idempotent, so this is safe to cache
|
|
120
|
+
* (1h stale). For hosts that want addresses without a live deposit session
|
|
121
|
+
* (e.g. pre-rendering a receive screen). Shares its cache key with the modal
|
|
122
|
+
* SDK's internal hook, so mixing surfaces never double-fetches.
|
|
123
|
+
*/
|
|
124
|
+
declare function useDepositAddresses(options: UseDepositAddressesOptions): _tanstack_react_query.UseQueryResult<DepositAddress[], Error>;
|
|
125
|
+
|
|
126
|
+
interface UseSupportedDepositTokensOptions {
|
|
127
|
+
/** All three destination fields must be provided together to filter. */
|
|
128
|
+
destination?: {
|
|
129
|
+
chainType: string;
|
|
130
|
+
chainId: string;
|
|
131
|
+
tokenAddress: string;
|
|
132
|
+
};
|
|
133
|
+
productType?: ProductType;
|
|
134
|
+
/** Whether the query should execute. Defaults to true. */
|
|
135
|
+
enabled?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Source tokens/chains a user can deposit from — for building custom
|
|
139
|
+
* token/chain pickers. Cached 5 minutes; deduped with the modal SDK.
|
|
140
|
+
*/
|
|
141
|
+
declare function useSupportedDepositTokens(options?: UseSupportedDepositTokensOptions): _tanstack_react_query.UseQueryResult<SupportedToken[], Error>;
|
|
142
|
+
|
|
143
|
+
interface UseExecutionsOptions {
|
|
144
|
+
/** Host platform's stable user identifier (maps to external_user_id). */
|
|
145
|
+
externalUserId: string | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Polling interval in milliseconds; `false` (default) disables polling.
|
|
148
|
+
* Deposit detection should use `useDeposit` — this hook is for
|
|
149
|
+
* history/tracker screens.
|
|
150
|
+
*/
|
|
151
|
+
refetchInterval?: number | false;
|
|
152
|
+
/** Whether the query should execute. Defaults to true. */
|
|
153
|
+
enabled?: boolean;
|
|
154
|
+
/** Filter by action type. Defaults to Deposit. */
|
|
155
|
+
actionType?: ActionType;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The user's executions (deposit history), mapped to camelCase
|
|
159
|
+
* {@link DirectExecution}. Shares its cache key with the modal SDK's tracker.
|
|
160
|
+
*/
|
|
161
|
+
declare function useExecutions(options: UseExecutionsOptions): _tanstack_react_query.UseQueryResult<DirectExecution[], Error>;
|
|
162
|
+
|
|
163
|
+
interface AllowedCountryResult {
|
|
164
|
+
/** Whether the user is in an allowed country/subdivision. null while loading. */
|
|
165
|
+
isAllowed: boolean | null;
|
|
166
|
+
/** ISO 3166-1 alpha-2 country code (e.g. "US", "PT") */
|
|
167
|
+
alpha2: string | null;
|
|
168
|
+
/** Full country name (e.g. "United States") */
|
|
169
|
+
country: string | null;
|
|
170
|
+
/** ISO 3166-2 subdivision code (e.g. "NY"), null if unavailable */
|
|
171
|
+
subdivisionCode: string | null;
|
|
172
|
+
isLoading: boolean;
|
|
173
|
+
error: Error | null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Whether the current user's region is allowed by the project's blocked
|
|
177
|
+
* countries configuration — the same geo gate the modal runs before showing
|
|
178
|
+
* deposit rails.
|
|
179
|
+
*
|
|
180
|
+
* The headless SDK is deliberately not IP-aware: `useDeposit` never runs this
|
|
181
|
+
* check. Hosts that want the modal's geo behavior opt in by rendering
|
|
182
|
+
* against this hook (and gating their deposit UI on `isAllowed`).
|
|
183
|
+
*/
|
|
184
|
+
declare function useAllowedCountry(): AllowedCountryResult;
|
|
185
|
+
|
|
186
|
+
interface UseAddressValidationOptions {
|
|
187
|
+
/** Address that will receive the deposited funds. */
|
|
188
|
+
recipientAddress?: string;
|
|
189
|
+
destination?: {
|
|
190
|
+
chainType: ChainType;
|
|
191
|
+
chainId: string;
|
|
192
|
+
tokenAddress: string;
|
|
193
|
+
};
|
|
194
|
+
/** Whether to run validation. Defaults to true. */
|
|
195
|
+
enabled?: boolean;
|
|
196
|
+
}
|
|
197
|
+
interface AddressValidationResult {
|
|
198
|
+
/** Whether the address can receive funds. null while loading or when params are incomplete. */
|
|
199
|
+
isValid: boolean | null;
|
|
200
|
+
/** Standardized error code for i18n. */
|
|
201
|
+
failureCode: AddressValidationFailureCode | null;
|
|
202
|
+
/** Metadata for message interpolation. */
|
|
203
|
+
metadata: {
|
|
204
|
+
chain_name?: string;
|
|
205
|
+
token_symbol?: string;
|
|
206
|
+
} | null;
|
|
207
|
+
isLoading: boolean;
|
|
208
|
+
error: Error | null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Validate that a recipient address can receive funds for a destination
|
|
212
|
+
* (primarily Algorand asset opt-in; typically valid=true elsewhere).
|
|
213
|
+
* `useDeposit` already fails fast on invalid recipients; use this hook for
|
|
214
|
+
* inline form validation before starting a session.
|
|
215
|
+
*/
|
|
216
|
+
declare function useAddressValidation(options: UseAddressValidationOptions): AddressValidationResult;
|
|
217
|
+
|
|
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 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
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';
|
|
4
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a memoized {@link UnifoldClient} configured with the publishable key
|
|
8
|
+
* from the surrounding `UnifoldProvider`.
|
|
9
|
+
*
|
|
10
|
+
* Escape hatch for imperative access to the headless API (resource calls,
|
|
11
|
+
* `createDepositSession`) outside the provided hooks.
|
|
12
|
+
*/
|
|
13
|
+
declare function useUnifoldClient(): UnifoldClient | null;
|
|
14
|
+
|
|
15
|
+
interface UseDepositDestination {
|
|
16
|
+
chainType: ChainType;
|
|
17
|
+
chainId: string;
|
|
18
|
+
tokenAddress: string;
|
|
19
|
+
/** Address that receives the deposited funds. */
|
|
20
|
+
recipientAddress: string;
|
|
21
|
+
/** EVM-only post-delivery calls; same constraints as DepositConfig.contractCalls. */
|
|
22
|
+
contractCalls?: EvmContractCall[];
|
|
23
|
+
}
|
|
24
|
+
interface UseDepositOptions {
|
|
25
|
+
/** Host platform's stable user identifier (maps to external_user_id). */
|
|
26
|
+
externalUserId: string | undefined;
|
|
27
|
+
/** Destination — what the deposit converts into and where it lands. */
|
|
28
|
+
destination: UseDepositDestination;
|
|
29
|
+
/**
|
|
30
|
+
* When the backend scan nudge is armed: 'auto' (default, after 5s) or
|
|
31
|
+
* 'manual' (host calls confirmFundsSent() from its own "I've sent it" button).
|
|
32
|
+
*/
|
|
33
|
+
confirmationMode?: 'auto' | 'manual';
|
|
34
|
+
/** Rail hint stamped onto emitted events' `method`. @default 'transfer' */
|
|
35
|
+
method?: DepositMethod;
|
|
36
|
+
/**
|
|
37
|
+
* When true (default), start() is called automatically once externalUserId and the
|
|
38
|
+
* provider's publishable key are present. Set false to gate on your own UI
|
|
39
|
+
* (e.g. only start when the user opens your deposit sheet).
|
|
40
|
+
*/
|
|
41
|
+
autoStart?: boolean;
|
|
42
|
+
/** Every lifecycle event ('*' subscription) — analytics/debug fan-out. */
|
|
43
|
+
onEvent?: (event: DepositSessionEvent) => void;
|
|
44
|
+
/** deposit_session.addresses_created */
|
|
45
|
+
onAddressesReady?: (addresses: DepositAddress[]) => void;
|
|
46
|
+
/** direct_execution.detected */
|
|
47
|
+
onExecutionDetected?: (execution: DirectExecution) => void;
|
|
48
|
+
/** direct_execution.updated */
|
|
49
|
+
onExecutionUpdated?: (execution: DirectExecution & {
|
|
50
|
+
previousStatus: ExecutionStatus | null;
|
|
51
|
+
}) => void;
|
|
52
|
+
/** direct_execution.succeeded */
|
|
53
|
+
onSuccess?: (execution: DirectExecution) => void;
|
|
54
|
+
/** direct_execution.failed + deposit_session.errored */
|
|
55
|
+
onError?: (error: DepositSessionError) => void;
|
|
56
|
+
/** Fired whenever snapshot.status changes. */
|
|
57
|
+
onStatusChange?: (status: DepositSessionStatus) => void;
|
|
58
|
+
}
|
|
59
|
+
interface UseDepositResult {
|
|
60
|
+
status: DepositSessionStatus;
|
|
61
|
+
/** Deposit addresses (one per chain type); empty until created. */
|
|
62
|
+
addresses: DepositAddress[];
|
|
63
|
+
/**
|
|
64
|
+
* Convenience: the deposit address matching the query. Takes an options
|
|
65
|
+
* object so future selectors (e.g. `addressType` when a chain type exposes
|
|
66
|
+
* multiple address formats) can be added without a breaking change.
|
|
67
|
+
*/
|
|
68
|
+
getAddress: (query: {
|
|
69
|
+
chainType: ChainType;
|
|
70
|
+
}) => DepositAddress | undefined;
|
|
71
|
+
/** All executions observed this session, newest first. */
|
|
72
|
+
executions: DirectExecution[];
|
|
73
|
+
latestExecution: DirectExecution | null;
|
|
74
|
+
/** True while the backend is actively checking for the deposit (scan nudge armed). */
|
|
75
|
+
isCheckingDeposit: boolean;
|
|
76
|
+
error: DepositSessionError | null;
|
|
77
|
+
/** Start the session (relevant when autoStart: false, or to retry after error). */
|
|
78
|
+
start: () => Promise<void>;
|
|
79
|
+
/** Arm the backend scan nudge in 'manual' mode ("I've sent it" button). */
|
|
80
|
+
confirmFundsSent: () => void;
|
|
81
|
+
/** Stop all polling. */
|
|
82
|
+
stop: () => void;
|
|
83
|
+
/** stop() + fresh baseline + start(). */
|
|
84
|
+
restart: () => Promise<void>;
|
|
85
|
+
/** Escape hatch to the underlying session (e.g. extra event subscriptions). */
|
|
86
|
+
session: DepositSession | null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Flagship headless deposit hook — the no-UI equivalent of
|
|
90
|
+
* `beginDeposit({ initialScreen: 'transfer' })`.
|
|
91
|
+
*
|
|
92
|
+
* Wraps a core {@link DepositSession}: creates it, subscribes via
|
|
93
|
+
* `useSyncExternalStore`, destroys it on unmount, and exposes the snapshot,
|
|
94
|
+
* imperative controls, and callback sugar. The host renders 100% of the UI.
|
|
95
|
+
*
|
|
96
|
+
* SSR-safe: the session is constructed in an effect; server renders see
|
|
97
|
+
* `status: 'idle'` and empty arrays.
|
|
98
|
+
*/
|
|
99
|
+
declare function useDeposit(options: UseDepositOptions): UseDepositResult;
|
|
100
|
+
|
|
101
|
+
interface UseDepositAddressesOptions {
|
|
102
|
+
/** Host platform's stable user identifier (maps to external_user_id). */
|
|
103
|
+
externalUserId: string | undefined;
|
|
104
|
+
destination?: {
|
|
105
|
+
chainType?: ChainType;
|
|
106
|
+
chainId?: string;
|
|
107
|
+
tokenAddress?: string;
|
|
108
|
+
recipientAddress?: string;
|
|
109
|
+
contractCalls?: EvmContractCall[];
|
|
110
|
+
};
|
|
111
|
+
/** Action type for the deposit address. @default server default ('deposit') */
|
|
112
|
+
actionType?: ActionType;
|
|
113
|
+
/** Whether the query should execute. Defaults to true. */
|
|
114
|
+
enabled?: boolean;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Fetch/create the user's deposit addresses with react-query caching.
|
|
118
|
+
*
|
|
119
|
+
* `POST /v1/public/deposit_addresses` is idempotent, so this is safe to cache
|
|
120
|
+
* (1h stale). For hosts that want addresses without a live deposit session
|
|
121
|
+
* (e.g. pre-rendering a receive screen). Shares its cache key with the modal
|
|
122
|
+
* SDK's internal hook, so mixing surfaces never double-fetches.
|
|
123
|
+
*/
|
|
124
|
+
declare function useDepositAddresses(options: UseDepositAddressesOptions): _tanstack_react_query.UseQueryResult<DepositAddress[], Error>;
|
|
125
|
+
|
|
126
|
+
interface UseSupportedDepositTokensOptions {
|
|
127
|
+
/** All three destination fields must be provided together to filter. */
|
|
128
|
+
destination?: {
|
|
129
|
+
chainType: string;
|
|
130
|
+
chainId: string;
|
|
131
|
+
tokenAddress: string;
|
|
132
|
+
};
|
|
133
|
+
productType?: ProductType;
|
|
134
|
+
/** Whether the query should execute. Defaults to true. */
|
|
135
|
+
enabled?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Source tokens/chains a user can deposit from — for building custom
|
|
139
|
+
* token/chain pickers. Cached 5 minutes; deduped with the modal SDK.
|
|
140
|
+
*/
|
|
141
|
+
declare function useSupportedDepositTokens(options?: UseSupportedDepositTokensOptions): _tanstack_react_query.UseQueryResult<SupportedToken[], Error>;
|
|
142
|
+
|
|
143
|
+
interface UseExecutionsOptions {
|
|
144
|
+
/** Host platform's stable user identifier (maps to external_user_id). */
|
|
145
|
+
externalUserId: string | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Polling interval in milliseconds; `false` (default) disables polling.
|
|
148
|
+
* Deposit detection should use `useDeposit` — this hook is for
|
|
149
|
+
* history/tracker screens.
|
|
150
|
+
*/
|
|
151
|
+
refetchInterval?: number | false;
|
|
152
|
+
/** Whether the query should execute. Defaults to true. */
|
|
153
|
+
enabled?: boolean;
|
|
154
|
+
/** Filter by action type. Defaults to Deposit. */
|
|
155
|
+
actionType?: ActionType;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The user's executions (deposit history), mapped to camelCase
|
|
159
|
+
* {@link DirectExecution}. Shares its cache key with the modal SDK's tracker.
|
|
160
|
+
*/
|
|
161
|
+
declare function useExecutions(options: UseExecutionsOptions): _tanstack_react_query.UseQueryResult<DirectExecution[], Error>;
|
|
162
|
+
|
|
163
|
+
interface AllowedCountryResult {
|
|
164
|
+
/** Whether the user is in an allowed country/subdivision. null while loading. */
|
|
165
|
+
isAllowed: boolean | null;
|
|
166
|
+
/** ISO 3166-1 alpha-2 country code (e.g. "US", "PT") */
|
|
167
|
+
alpha2: string | null;
|
|
168
|
+
/** Full country name (e.g. "United States") */
|
|
169
|
+
country: string | null;
|
|
170
|
+
/** ISO 3166-2 subdivision code (e.g. "NY"), null if unavailable */
|
|
171
|
+
subdivisionCode: string | null;
|
|
172
|
+
isLoading: boolean;
|
|
173
|
+
error: Error | null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Whether the current user's region is allowed by the project's blocked
|
|
177
|
+
* countries configuration — the same geo gate the modal runs before showing
|
|
178
|
+
* deposit rails.
|
|
179
|
+
*
|
|
180
|
+
* The headless SDK is deliberately not IP-aware: `useDeposit` never runs this
|
|
181
|
+
* check. Hosts that want the modal's geo behavior opt in by rendering
|
|
182
|
+
* against this hook (and gating their deposit UI on `isAllowed`).
|
|
183
|
+
*/
|
|
184
|
+
declare function useAllowedCountry(): AllowedCountryResult;
|
|
185
|
+
|
|
186
|
+
interface UseAddressValidationOptions {
|
|
187
|
+
/** Address that will receive the deposited funds. */
|
|
188
|
+
recipientAddress?: string;
|
|
189
|
+
destination?: {
|
|
190
|
+
chainType: ChainType;
|
|
191
|
+
chainId: string;
|
|
192
|
+
tokenAddress: string;
|
|
193
|
+
};
|
|
194
|
+
/** Whether to run validation. Defaults to true. */
|
|
195
|
+
enabled?: boolean;
|
|
196
|
+
}
|
|
197
|
+
interface AddressValidationResult {
|
|
198
|
+
/** Whether the address can receive funds. null while loading or when params are incomplete. */
|
|
199
|
+
isValid: boolean | null;
|
|
200
|
+
/** Standardized error code for i18n. */
|
|
201
|
+
failureCode: AddressValidationFailureCode | null;
|
|
202
|
+
/** Metadata for message interpolation. */
|
|
203
|
+
metadata: {
|
|
204
|
+
chain_name?: string;
|
|
205
|
+
token_symbol?: string;
|
|
206
|
+
} | null;
|
|
207
|
+
isLoading: boolean;
|
|
208
|
+
error: Error | null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Validate that a recipient address can receive funds for a destination
|
|
212
|
+
* (primarily Algorand asset opt-in; typically valid=true elsewhere).
|
|
213
|
+
* `useDeposit` already fails fast on invalid recipients; use this hook for
|
|
214
|
+
* inline form validation before starting a session.
|
|
215
|
+
*/
|
|
216
|
+
declare function useAddressValidation(options: UseAddressValidationOptions): AddressValidationResult;
|
|
217
|
+
|
|
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 };
|