@defuse-protocol/nearintents-mpp-sdk 0.0.0 → 0.1.0

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -0
  3. package/dist/Errors.d.ts +57 -0
  4. package/dist/Errors.d.ts.map +1 -0
  5. package/dist/Errors.js +54 -0
  6. package/dist/Errors.js.map +1 -0
  7. package/dist/Methods.d.ts +44 -0
  8. package/dist/Methods.d.ts.map +1 -0
  9. package/dist/Methods.js +23 -0
  10. package/dist/Methods.js.map +1 -0
  11. package/dist/Types.d.ts +125 -0
  12. package/dist/Types.d.ts.map +1 -0
  13. package/dist/Types.js +195 -0
  14. package/dist/Types.js.map +1 -0
  15. package/dist/client/Charge.d.ts +141 -0
  16. package/dist/client/Charge.d.ts.map +1 -0
  17. package/dist/client/Charge.js +167 -0
  18. package/dist/client/Charge.js.map +1 -0
  19. package/dist/client/index.d.ts +5 -0
  20. package/dist/client/index.d.ts.map +1 -0
  21. package/dist/client/index.js +5 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/index.d.ts +5 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +5 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/internal/OneClick.d.ts +243 -0
  28. package/dist/internal/OneClick.d.ts.map +1 -0
  29. package/dist/internal/OneClick.js +422 -0
  30. package/dist/internal/OneClick.js.map +1 -0
  31. package/dist/server/Charge.d.ts +241 -0
  32. package/dist/server/Charge.d.ts.map +1 -0
  33. package/dist/server/Charge.js +404 -0
  34. package/dist/server/Charge.js.map +1 -0
  35. package/dist/server/index.d.ts +5 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +5 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/package.json +87 -9
  40. package/src/Errors.ts +86 -0
  41. package/src/Methods.ts +24 -0
  42. package/src/Types.ts +256 -0
  43. package/src/client/Charge.ts +268 -0
  44. package/src/client/index.ts +4 -0
  45. package/src/index.ts +4 -0
  46. package/src/internal/OneClick.ts +634 -0
  47. package/src/server/Charge.ts +621 -0
  48. package/src/server/index.ts +4 -0
@@ -0,0 +1,243 @@
1
+ import { Errors } from 'mppx';
2
+ /**
3
+ * 1Click Swap API settlement core.
4
+ *
5
+ * Thin, dependency-free client for the three endpoints this method uses
6
+ * (`POST /v0/quote`, `POST /v0/deposit/submit`, `GET /v0/status`) plus the
7
+ * token-list-driven CAIP-19 ↔ 1Click asset mapping and the terminal-state →
8
+ * MPP problem-type mapping. Everything is injectable (`fetch`, `baseUrl`) so
9
+ * tests run against the in-process mock — never live 1Click.
10
+ *
11
+ * Designed to be shared with the x402 1CS Gateway workstream: one settlement
12
+ * core, two protocol adapters.
13
+ */
14
+ export declare const defaultBaseUrl = "https://1click.chaindefuser.com";
15
+ export type Config = {
16
+ /** API base URL. @default "https://1click.chaindefuser.com" */
17
+ baseUrl?: string | undefined;
18
+ /**
19
+ * Partner JWT (env `ONE_CLICK_JWT`). Server-side only — MUST never appear
20
+ * in any challenge field. Unauthenticated requests incur a 0.2% fee.
21
+ */
22
+ jwt?: string | undefined;
23
+ /** Fetch implementation. @default globalThis.fetch */
24
+ fetch?: typeof globalThis.fetch | undefined;
25
+ /** Per-request timeout in milliseconds. @default 30000 */
26
+ requestTimeoutMs?: number | undefined;
27
+ /** Additional CAIP-2 → 1Click blockchain-code entries, merged over {@link defaultNetworks}. */
28
+ networks?: Record<string, string> | undefined;
29
+ /** Additional blockchain-code → SLIP-44 coin-type entries, merged over {@link defaultNativeCoinTypes}. */
30
+ nativeCoinTypes?: Record<string, number> | undefined;
31
+ };
32
+ /**
33
+ * The 1Click API request failed (application error or unusable response).
34
+ * Note {@link OneClickUnavailableError} (network failure / 5xx) is a subclass —
35
+ * narrow on it first when the distinction matters.
36
+ */
37
+ export declare class OneClickError extends Error {
38
+ readonly name: string;
39
+ readonly status: number;
40
+ readonly body: unknown;
41
+ constructor(message: string, options?: {
42
+ status?: number;
43
+ body?: unknown;
44
+ cause?: unknown;
45
+ });
46
+ }
47
+ /**
48
+ * The 1Click API was unreachable or returned a server error (network failure
49
+ * or HTTP 5xx). Per the spec, callers MUST surface this as an HTTP 5xx —
50
+ * never as `verification-failed` — and MUST NOT settle the credential.
51
+ */
52
+ export declare class OneClickUnavailableError extends OneClickError {
53
+ readonly name = "OneClickUnavailableError";
54
+ }
55
+ /** `pollToTerminal` exceeded its time budget without reaching a terminal status. */
56
+ export declare class PollTimeoutError extends Error {
57
+ readonly name = "PollTimeoutError";
58
+ /** Last successfully observed status, if any poll succeeded. */
59
+ readonly lastStatus: SwapStatus | undefined;
60
+ /** Last transport error, if polling was failing when the budget ran out. */
61
+ readonly lastError: unknown;
62
+ constructor(options: {
63
+ timeoutMs: number;
64
+ lastStatus?: SwapStatus;
65
+ lastError?: unknown;
66
+ });
67
+ }
68
+ export type TokenInfo = {
69
+ /** 1Click-native asset id (e.g. `nep141:arb-0xaf88….omft.near`). */
70
+ assetId: string;
71
+ decimals: number;
72
+ /** 1Click blockchain code (e.g. "arb", "btc", "near"). */
73
+ blockchain: string;
74
+ symbol: string;
75
+ /** Absent for chain-native assets (e.g. BTC). */
76
+ contractAddress?: string | undefined;
77
+ };
78
+ export type Quote = {
79
+ /** Unique single-use deposit address on the origin chain. */
80
+ depositAddress: string;
81
+ /** Required alongside the deposit on memo-based chains (e.g. Stellar). */
82
+ depositMemo?: string | undefined;
83
+ /** Input amount with slippage buffer baked in — the challenge `amount`. */
84
+ amountIn: string;
85
+ /** Minimum input that still guarantees `amountOut` — the verification threshold. */
86
+ minAmountIn: string;
87
+ /** Exact output amount the merchant receives (EXACT_OUTPUT). */
88
+ amountOut: string;
89
+ minAmountOut?: string | undefined;
90
+ /** Deposit deadline (ISO 8601) — the challenge `expires` upper bound. */
91
+ deadline: string;
92
+ timeWhenInactive?: string | undefined;
93
+ /** Estimated swap completion time in seconds after deposit confirmation. */
94
+ timeEstimate: number;
95
+ };
96
+ export type QuoteResult = {
97
+ quote: Quote;
98
+ /** Echo of the quote request. */
99
+ quoteRequest: Record<string, unknown>;
100
+ /** 1Click service signature over the quote — persist for dispute resolution. */
101
+ signature?: string | undefined;
102
+ timestamp?: string | undefined;
103
+ };
104
+ export type SwapStatus = 'KNOWN_DEPOSIT_TX' | 'PENDING_DEPOSIT' | 'INCOMPLETE_DEPOSIT' | 'PROCESSING' | 'SUCCESS' | 'REFUNDED' | 'FAILED';
105
+ export type TransactionDetails = {
106
+ hash: string;
107
+ explorerUrl?: string | undefined;
108
+ };
109
+ export type SwapDetails = {
110
+ originChainTxHashes?: TransactionDetails[] | undefined;
111
+ destinationChainTxHashes?: TransactionDetails[] | undefined;
112
+ intentHashes?: string[] | undefined;
113
+ nearTxHashes?: string[] | undefined;
114
+ amountIn?: string | undefined;
115
+ amountOut?: string | undefined;
116
+ depositedAmount?: string | undefined;
117
+ refundedAmount?: string | undefined;
118
+ refundReason?: string | undefined;
119
+ };
120
+ export type StatusResult = {
121
+ status: SwapStatus;
122
+ updatedAt?: string | undefined;
123
+ swapDetails?: SwapDetails | undefined;
124
+ quoteResponse?: Record<string, unknown> | undefined;
125
+ };
126
+ /**
127
+ * Terminal settlement statuses per the spec (§Settlement Procedure): the swap
128
+ * either delivered (`SUCCESS`) or the deposit is refunded to `refundTo`.
129
+ * `INCOMPLETE_DEPOSIT` is treated as terminal for settlement even though the
130
+ * backend may still accept top-ups before the deadline.
131
+ */
132
+ export declare const terminalStatuses: readonly ["SUCCESS", "FAILED", "REFUNDED", "INCOMPLETE_DEPOSIT"];
133
+ export declare function isTerminal(status: SwapStatus): boolean;
134
+ /** Fetches the supported token list (`GET /v0/tokens`). No auth required. */
135
+ export declare function getTokens(config: Config): Promise<TokenInfo[]>;
136
+ export type QuoteParameters = {
137
+ /** Origin asset as a 1Click asset id (map from CAIP-19 via {@link AssetMap}). */
138
+ originAsset: string;
139
+ /** Destination asset as a 1Click asset id. */
140
+ destinationAsset: string;
141
+ /** Exact output amount the merchant must receive, in base units (EXACT_OUTPUT). */
142
+ amountOut: string;
143
+ /** Merchant address on the destination chain. */
144
+ recipient: string;
145
+ /** Merchant-configured refund address on the origin chain. */
146
+ refundTo: string;
147
+ /** Slippage tolerance in basis points (applied to the input side). */
148
+ slippageTolerance: number;
149
+ /** Quote/deposit deadline (ISO 8601). Size to the origin chain (BTC ≈ 45–60 min). */
150
+ deadline: string;
151
+ /** `SIMPLE` (default) or `MEMO` for memo-based chains. */
152
+ depositMode?: 'SIMPLE' | 'MEMO' | undefined;
153
+ referral?: string | undefined;
154
+ sessionId?: string | undefined;
155
+ quoteWaitingTimeMs?: number | undefined;
156
+ /** Escape hatch merged last into the raw request body. */
157
+ overrides?: Record<string, unknown> | undefined;
158
+ };
159
+ /**
160
+ * Requests a wet (`dry: false`) EXACT_OUTPUT quote (`POST /v0/quote`),
161
+ * yielding the unique single-use deposit address for one challenge.
162
+ */
163
+ export declare function quote(config: Config, parameters: QuoteParameters): Promise<QuoteResult>;
164
+ /**
165
+ * Notifies 1Click of a sent deposit (`POST /v0/deposit/submit`). Optional but
166
+ * recommended after verification — it accelerates deposit detection.
167
+ */
168
+ export declare function submitDeposit(config: Config, parameters: {
169
+ txHash: string;
170
+ depositAddress: string;
171
+ memo?: string | undefined;
172
+ }): Promise<StatusResult>;
173
+ /** Fetches the swap status for a deposit address (`GET /v0/status`). */
174
+ export declare function getStatus(config: Config, parameters: {
175
+ depositAddress: string;
176
+ depositMemo?: string | undefined;
177
+ }): Promise<StatusResult>;
178
+ export type PollParameters = {
179
+ depositAddress: string;
180
+ depositMemo?: string | undefined;
181
+ /** Total time budget. Derive from the challenge window + `timeEstimate` + margin. */
182
+ timeoutMs: number;
183
+ /** Delay between polls. @default 2000 */
184
+ intervalMs?: number | undefined;
185
+ /** Invoked once per observed status change (not per poll tick). */
186
+ onStatus?: ((status: SwapStatus) => void) | undefined;
187
+ signal?: AbortSignal | undefined;
188
+ };
189
+ /**
190
+ * Polls the status endpoint until a terminal status ({@link terminalStatuses})
191
+ * or the time budget is exceeded.
192
+ *
193
+ * Transient transport failures ({@link OneClickUnavailableError}) are retried
194
+ * until the budget runs out; if the budget ends on a failing endpoint the last
195
+ * error is rethrown (so callers surface 5xx, not a false timeout). A budget
196
+ * that ends while the swap is still in flight throws {@link PollTimeoutError}.
197
+ */
198
+ export declare function pollToTerminal(config: Config, parameters: PollParameters): Promise<StatusResult>;
199
+ /**
200
+ * Maps a non-success terminal status to its MPP problem type:
201
+ * `INCOMPLETE_DEPOSIT` → `payment-insufficient`; `FAILED`/`REFUNDED` →
202
+ * `settlement-failed`. Returns `undefined` for `SUCCESS`. Throws if the
203
+ * status is not terminal.
204
+ */
205
+ export declare function terminalError(result: StatusResult): Errors.PaymentError | undefined;
206
+ /** Extracts the destination-chain delivery tx hash (the receipt `reference`). */
207
+ export declare function destinationTxHash(result: StatusResult): string | undefined;
208
+ /**
209
+ * Checks whether the credential's `payload.hash` is among the origin-chain
210
+ * transactions the backend observed for this deposit address — the spec's
211
+ * status-observation deposit confirmation. Hex hashes compare
212
+ * case-insensitively; other formats (base58, …) compare exactly.
213
+ */
214
+ export declare function matchesOriginTx(result: StatusResult, hash: string): boolean;
215
+ /**
216
+ * CAIP-2 chain identifier → 1Click blockchain code, for chains whose CAIP-2
217
+ * form is well established. Extensible per-instance via `Config.networks` —
218
+ * unsupported chains need a config entry, not a package release.
219
+ */
220
+ export declare const defaultNetworks: Record<string, string>;
221
+ /**
222
+ * 1Click blockchain code → SLIP-44 coin type used to render chain-native
223
+ * assets (`<caip2>/slip44:<coinType>`). Extensible via `Config.nativeCoinTypes`.
224
+ */
225
+ export declare const defaultNativeCoinTypes: Record<string, number>;
226
+ export type AssetMap = {
227
+ /** Resolves a CAIP-19 asset id to the matching 1Click token, or `undefined`. */
228
+ tokenOf(caip19Id: string): TokenInfo | undefined;
229
+ /** Resolves a CAIP-19 asset id to a 1Click asset id, or `undefined`. */
230
+ toAssetId(caip19Id: string): string | undefined;
231
+ /** Renders a 1Click asset id as a CAIP-19 asset id, or `undefined` when unmapped. */
232
+ toCaip19(assetId: string): string | undefined;
233
+ };
234
+ /**
235
+ * Builds a bidirectional CAIP-19 ↔ 1Click asset map from the `/v0/tokens`
236
+ * list. Contract assets match on `contractAddress` (case-insensitively for
237
+ * `eip155` chains); `slip44` assets match the chain's token that has no
238
+ * `contractAddress` (chain-native), and only for the chain's canonical coin
239
+ * type — a wrong coin type resolves to nothing rather than silently mapping
240
+ * to the native asset.
241
+ */
242
+ export declare function createAssetMap(tokens: TokenInfo[], config?: Config): AssetMap;
243
+ //# sourceMappingURL=OneClick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OneClick.d.ts","sourceRoot":"","sources":["../../src/internal/OneClick.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,422 @@
1
+ import { Errors } from 'mppx';
2
+ import { SettlementFailedError } from '../Errors.js';
3
+ import * as Types from '../Types.js';
4
+ /**
5
+ * 1Click Swap API settlement core.
6
+ *
7
+ * Thin, dependency-free client for the three endpoints this method uses
8
+ * (`POST /v0/quote`, `POST /v0/deposit/submit`, `GET /v0/status`) plus the
9
+ * token-list-driven CAIP-19 ↔ 1Click asset mapping and the terminal-state →
10
+ * MPP problem-type mapping. Everything is injectable (`fetch`, `baseUrl`) so
11
+ * tests run against the in-process mock — never live 1Click.
12
+ *
13
+ * Designed to be shared with the x402 1CS Gateway workstream: one settlement
14
+ * core, two protocol adapters.
15
+ */
16
+ export const defaultBaseUrl = 'https://1click.chaindefuser.com';
17
+ // ---------------------------------------------------------------------------
18
+ // Errors
19
+ // ---------------------------------------------------------------------------
20
+ /**
21
+ * The 1Click API request failed (application error or unusable response).
22
+ * Note {@link OneClickUnavailableError} (network failure / 5xx) is a subclass —
23
+ * narrow on it first when the distinction matters.
24
+ */
25
+ export class OneClickError extends Error {
26
+ name = 'OneClickError';
27
+ status;
28
+ body;
29
+ constructor(message, options = {}) {
30
+ super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
31
+ this.status = options.status ?? 0;
32
+ this.body = options.body;
33
+ }
34
+ }
35
+ /**
36
+ * The 1Click API was unreachable or returned a server error (network failure
37
+ * or HTTP 5xx). Per the spec, callers MUST surface this as an HTTP 5xx —
38
+ * never as `verification-failed` — and MUST NOT settle the credential.
39
+ */
40
+ export class OneClickUnavailableError extends OneClickError {
41
+ name = 'OneClickUnavailableError';
42
+ }
43
+ /** `pollToTerminal` exceeded its time budget without reaching a terminal status. */
44
+ export class PollTimeoutError extends Error {
45
+ name = 'PollTimeoutError';
46
+ /** Last successfully observed status, if any poll succeeded. */
47
+ lastStatus;
48
+ /** Last transport error, if polling was failing when the budget ran out. */
49
+ lastError;
50
+ constructor(options) {
51
+ super(`1Click status polling timed out after ${options.timeoutMs}ms` +
52
+ (options.lastStatus ? ` (last status: ${options.lastStatus})` : ''));
53
+ this.lastStatus = options.lastStatus;
54
+ this.lastError = options.lastError;
55
+ }
56
+ }
57
+ /**
58
+ * Terminal settlement statuses per the spec (§Settlement Procedure): the swap
59
+ * either delivered (`SUCCESS`) or the deposit is refunded to `refundTo`.
60
+ * `INCOMPLETE_DEPOSIT` is treated as terminal for settlement even though the
61
+ * backend may still accept top-ups before the deadline.
62
+ */
63
+ export const terminalStatuses = ['SUCCESS', 'FAILED', 'REFUNDED', 'INCOMPLETE_DEPOSIT'];
64
+ export function isTerminal(status) {
65
+ return terminalStatuses.includes(status);
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // HTTP core
69
+ // ---------------------------------------------------------------------------
70
+ async function request(config, path, init) {
71
+ const baseUrl = config.baseUrl ?? defaultBaseUrl;
72
+ const fetchFn = config.fetch ?? globalThis.fetch;
73
+ const url = new URL(path, baseUrl);
74
+ for (const [key, value] of Object.entries(init.searchParams ?? {})) {
75
+ url.searchParams.set(key, value);
76
+ }
77
+ let response;
78
+ try {
79
+ response = await fetchFn(url, {
80
+ method: init.method,
81
+ headers: {
82
+ accept: 'application/json',
83
+ ...(init.body !== undefined && { 'content-type': 'application/json' }),
84
+ ...(config.jwt && { authorization: `Bearer ${config.jwt}` }),
85
+ },
86
+ ...(init.body !== undefined && { body: JSON.stringify(init.body) }),
87
+ signal: AbortSignal.timeout(config.requestTimeoutMs ?? 30_000),
88
+ });
89
+ }
90
+ catch (error) {
91
+ throw new OneClickUnavailableError(`1Click request failed: ${init.method} ${path}`, {
92
+ cause: error,
93
+ });
94
+ }
95
+ const body = await response.json().catch(() => undefined);
96
+ if (response.status >= 500)
97
+ throw new OneClickUnavailableError(`1Click returned ${response.status} for ${init.method} ${path}`, { status: response.status, body });
98
+ if (!response.ok)
99
+ throw new OneClickError(`1Click returned ${response.status} for ${init.method} ${path}: ${detailOf(body)}`, { status: response.status, body });
100
+ return body;
101
+ }
102
+ function detailOf(body) {
103
+ if (body && typeof body === 'object' && 'message' in body)
104
+ return String(body.message);
105
+ return body === undefined ? 'no response body' : JSON.stringify(body);
106
+ }
107
+ // ---------------------------------------------------------------------------
108
+ // Endpoints
109
+ // ---------------------------------------------------------------------------
110
+ /** Fetches the supported token list (`GET /v0/tokens`). No auth required. */
111
+ export async function getTokens(config) {
112
+ const tokens = await request(config, '/v0/tokens', { method: 'GET' });
113
+ if (!Array.isArray(tokens))
114
+ throw new OneClickError('1Click token list response is not an array.', { body: tokens });
115
+ return tokens;
116
+ }
117
+ /**
118
+ * Requests a wet (`dry: false`) EXACT_OUTPUT quote (`POST /v0/quote`),
119
+ * yielding the unique single-use deposit address for one challenge.
120
+ */
121
+ export async function quote(config, parameters) {
122
+ const { amountOut, deadline, depositMode, destinationAsset, originAsset, overrides, quoteWaitingTimeMs, recipient, referral, refundTo, sessionId, slippageTolerance, } = parameters;
123
+ const body = {
124
+ dry: false,
125
+ swapType: 'EXACT_OUTPUT',
126
+ depositType: 'ORIGIN_CHAIN',
127
+ recipientType: 'DESTINATION_CHAIN',
128
+ refundType: 'ORIGIN_CHAIN',
129
+ amount: amountOut,
130
+ originAsset,
131
+ destinationAsset,
132
+ recipient,
133
+ refundTo,
134
+ slippageTolerance,
135
+ deadline,
136
+ ...(depositMode !== undefined && { depositMode }),
137
+ ...(referral !== undefined && { referral }),
138
+ ...(sessionId !== undefined && { sessionId }),
139
+ ...(quoteWaitingTimeMs !== undefined && { quoteWaitingTimeMs }),
140
+ ...overrides,
141
+ };
142
+ const result = await request(config, '/v0/quote', { method: 'POST', body });
143
+ const quote_ = result?.quote;
144
+ for (const field of ['depositAddress', 'amountIn', 'minAmountIn', 'deadline']) {
145
+ if (!quote_?.[field])
146
+ throw new OneClickError(`1Click quote response is missing "quote.${field}".`, {
147
+ body: result,
148
+ });
149
+ }
150
+ return {
151
+ quote: quote_,
152
+ quoteRequest: result.quoteRequest ?? body,
153
+ ...(hasString(result, 'signature') && { signature: result.signature }),
154
+ ...(hasString(result, 'timestamp') && { timestamp: result.timestamp }),
155
+ };
156
+ }
157
+ function hasString(value, key) {
158
+ return (typeof value === 'object' &&
159
+ value !== null &&
160
+ typeof value[key] === 'string');
161
+ }
162
+ /**
163
+ * Notifies 1Click of a sent deposit (`POST /v0/deposit/submit`). Optional but
164
+ * recommended after verification — it accelerates deposit detection.
165
+ */
166
+ export async function submitDeposit(config, parameters) {
167
+ const { depositAddress, memo, txHash } = parameters;
168
+ return request(config, '/v0/deposit/submit', {
169
+ method: 'POST',
170
+ body: { txHash, depositAddress, ...(memo !== undefined && { memo }) },
171
+ });
172
+ }
173
+ /** Fetches the swap status for a deposit address (`GET /v0/status`). */
174
+ export async function getStatus(config, parameters) {
175
+ const { depositAddress, depositMemo } = parameters;
176
+ return request(config, '/v0/status', {
177
+ method: 'GET',
178
+ searchParams: {
179
+ depositAddress,
180
+ ...(depositMemo !== undefined && { depositMemo }),
181
+ },
182
+ });
183
+ }
184
+ /**
185
+ * Polls the status endpoint until a terminal status ({@link terminalStatuses})
186
+ * or the time budget is exceeded.
187
+ *
188
+ * Transient transport failures ({@link OneClickUnavailableError}) are retried
189
+ * until the budget runs out; if the budget ends on a failing endpoint the last
190
+ * error is rethrown (so callers surface 5xx, not a false timeout). A budget
191
+ * that ends while the swap is still in flight throws {@link PollTimeoutError}.
192
+ */
193
+ export async function pollToTerminal(config, parameters) {
194
+ const { depositAddress, depositMemo, onStatus, signal, timeoutMs } = parameters;
195
+ const intervalMs = parameters.intervalMs ?? 2000;
196
+ const deadline = Date.now() + timeoutMs;
197
+ let lastStatus;
198
+ let lastError;
199
+ while (true) {
200
+ signal?.throwIfAborted();
201
+ try {
202
+ const result = await getStatus(config, { depositAddress, depositMemo });
203
+ if (result.status !== lastStatus)
204
+ onStatus?.(result.status);
205
+ lastStatus = result.status;
206
+ lastError = undefined;
207
+ if (isTerminal(result.status))
208
+ return result;
209
+ }
210
+ catch (error) {
211
+ if (!(error instanceof OneClickUnavailableError))
212
+ throw error;
213
+ lastError = error;
214
+ }
215
+ if (Date.now() + intervalMs > deadline) {
216
+ if (lastError !== undefined)
217
+ throw lastError;
218
+ throw new PollTimeoutError({
219
+ timeoutMs,
220
+ ...(lastStatus !== undefined && { lastStatus }),
221
+ });
222
+ }
223
+ await sleep(intervalMs, signal);
224
+ }
225
+ }
226
+ function sleep(ms, signal) {
227
+ return new Promise((resolve, reject) => {
228
+ if (signal?.aborted) {
229
+ reject(signal.reason ?? new Error('Aborted.'));
230
+ return;
231
+ }
232
+ const timer = setTimeout(() => {
233
+ signal?.removeEventListener('abort', onAbort);
234
+ resolve();
235
+ }, ms);
236
+ const onAbort = () => {
237
+ clearTimeout(timer);
238
+ reject(signal?.reason ?? new Error('Aborted.'));
239
+ };
240
+ signal?.addEventListener('abort', onAbort, { once: true });
241
+ });
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // Terminal-state → MPP problem mapping (spec §Error Codes)
245
+ // ---------------------------------------------------------------------------
246
+ /**
247
+ * Maps a non-success terminal status to its MPP problem type:
248
+ * `INCOMPLETE_DEPOSIT` → `payment-insufficient`; `FAILED`/`REFUNDED` →
249
+ * `settlement-failed`. Returns `undefined` for `SUCCESS`. Throws if the
250
+ * status is not terminal.
251
+ */
252
+ export function terminalError(result) {
253
+ const { status, swapDetails } = result;
254
+ switch (status) {
255
+ case 'SUCCESS':
256
+ return undefined;
257
+ case 'INCOMPLETE_DEPOSIT':
258
+ return new Errors.PaymentInsufficientError({
259
+ reason: swapDetails?.depositedAmount
260
+ ? `deposit of ${swapDetails.depositedAmount} is below the required minimum`
261
+ : 'deposit is below the required minimum',
262
+ });
263
+ case 'FAILED':
264
+ case 'REFUNDED':
265
+ return new SettlementFailedError({
266
+ reason: swapDetails?.refundReason ?? `1Click terminal status ${status}`,
267
+ });
268
+ default:
269
+ throw new Error(`Status "${status}" is not terminal.`);
270
+ }
271
+ }
272
+ /** Extracts the destination-chain delivery tx hash (the receipt `reference`). */
273
+ export function destinationTxHash(result) {
274
+ return result.swapDetails?.destinationChainTxHashes?.[0]?.hash;
275
+ }
276
+ /**
277
+ * Checks whether the credential's `payload.hash` is among the origin-chain
278
+ * transactions the backend observed for this deposit address — the spec's
279
+ * status-observation deposit confirmation. Hex hashes compare
280
+ * case-insensitively; other formats (base58, …) compare exactly.
281
+ */
282
+ export function matchesOriginTx(result, hash) {
283
+ const hashes = result.swapDetails?.originChainTxHashes ?? [];
284
+ return hashes.some((tx) => txHashEqual(tx.hash, hash));
285
+ }
286
+ function txHashEqual(a, b) {
287
+ const hexLike = /^(0x)?[0-9a-fA-F]{16,}$/;
288
+ if (hexLike.test(a) && hexLike.test(b))
289
+ return normalizeHex(a) === normalizeHex(b);
290
+ return a === b;
291
+ }
292
+ function normalizeHex(hash) {
293
+ return hash.toLowerCase().replace(/^0x/, '');
294
+ }
295
+ // ---------------------------------------------------------------------------
296
+ // CAIP-19 ↔ 1Click asset mapping (token-list driven)
297
+ // ---------------------------------------------------------------------------
298
+ /**
299
+ * CAIP-2 chain identifier → 1Click blockchain code, for chains whose CAIP-2
300
+ * form is well established. Extensible per-instance via `Config.networks` —
301
+ * unsupported chains need a config entry, not a package release.
302
+ */
303
+ export const defaultNetworks = {
304
+ 'near:mainnet': 'near',
305
+ 'eip155:1': 'eth',
306
+ 'eip155:10': 'op',
307
+ 'eip155:56': 'bsc',
308
+ 'eip155:100': 'gnosis',
309
+ 'eip155:137': 'pol',
310
+ 'eip155:143': 'monad',
311
+ 'eip155:196': 'xlayer',
312
+ 'eip155:8453': 'base',
313
+ 'eip155:42161': 'arb',
314
+ 'eip155:43114': 'avax',
315
+ 'eip155:80094': 'bera',
316
+ 'eip155:534352': 'scroll',
317
+ 'bip122:000000000019d6689c085ae165831e93': 'btc',
318
+ 'bip122:12a765e31ffd4059bada1e25190f6e98': 'ltc',
319
+ 'bip122:1a91e3dace36e2be3bf030a65679fe82': 'doge',
320
+ 'bip122:000000000000000000651ef99cb9fcbe': 'bch',
321
+ 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'sol',
322
+ 'ton:-239': 'ton',
323
+ 'sui:mainnet': 'sui',
324
+ 'stellar:pubnet': 'stellar',
325
+ 'xrpl:0': 'xrp',
326
+ 'starknet:SN_MAIN': 'starknet',
327
+ };
328
+ /**
329
+ * 1Click blockchain code → SLIP-44 coin type used to render chain-native
330
+ * assets (`<caip2>/slip44:<coinType>`). Extensible via `Config.nativeCoinTypes`.
331
+ */
332
+ export const defaultNativeCoinTypes = {
333
+ btc: 0,
334
+ ltc: 2,
335
+ doge: 3,
336
+ bch: 145,
337
+ eth: 60,
338
+ base: 60,
339
+ arb: 60,
340
+ op: 60,
341
+ scroll: 60,
342
+ bsc: 714,
343
+ pol: 966,
344
+ gnosis: 700,
345
+ avax: 9000,
346
+ xrp: 144,
347
+ sol: 501,
348
+ ton: 607,
349
+ stellar: 148,
350
+ sui: 784,
351
+ };
352
+ /** CAIP-19 asset-namespace used when rendering a contract asset for a chain namespace. */
353
+ const contractAssetNamespaces = {
354
+ eip155: 'erc20',
355
+ near: 'nep141',
356
+ solana: 'token',
357
+ };
358
+ /**
359
+ * Builds a bidirectional CAIP-19 ↔ 1Click asset map from the `/v0/tokens`
360
+ * list. Contract assets match on `contractAddress` (case-insensitively for
361
+ * `eip155` chains); `slip44` assets match the chain's token that has no
362
+ * `contractAddress` (chain-native), and only for the chain's canonical coin
363
+ * type — a wrong coin type resolves to nothing rather than silently mapping
364
+ * to the native asset.
365
+ */
366
+ export function createAssetMap(tokens, config = {}) {
367
+ const networks = { ...defaultNetworks, ...config.networks };
368
+ const nativeCoinTypes = { ...defaultNativeCoinTypes, ...config.nativeCoinTypes };
369
+ const blockchainToCaip2 = new Map();
370
+ for (const [caip2Id, code] of Object.entries(networks)) {
371
+ if (!blockchainToCaip2.has(code))
372
+ blockchainToCaip2.set(code, caip2Id);
373
+ }
374
+ function blockchainOf(caip19Id) {
375
+ const chain = Types.chainOf(caip19Id);
376
+ return Object.entries(networks).find(([caip2Id]) => Types.networkEqual(caip2Id, chain))?.[1];
377
+ }
378
+ function tokenOf(caip19Id) {
379
+ const parsed = Types.parseCaip19(caip19Id);
380
+ const blockchain = blockchainOf(caip19Id);
381
+ if (!blockchain)
382
+ return undefined;
383
+ if (parsed.assetNamespace === 'slip44') {
384
+ const coinType = nativeCoinTypes[blockchain];
385
+ if (coinType === undefined || parsed.assetReference !== String(coinType))
386
+ return undefined;
387
+ return tokens.find((token) => token.blockchain === blockchain && !token.contractAddress);
388
+ }
389
+ const caseInsensitive = parsed.chain.namespace === 'eip155';
390
+ const reference = caseInsensitive ? parsed.assetReference.toLowerCase() : parsed.assetReference;
391
+ return tokens.find((token) => {
392
+ if (token.blockchain !== blockchain || !token.contractAddress)
393
+ return false;
394
+ const address = caseInsensitive ? token.contractAddress.toLowerCase() : token.contractAddress;
395
+ return address === reference;
396
+ });
397
+ }
398
+ return {
399
+ tokenOf,
400
+ toAssetId(caip19Id) {
401
+ return tokenOf(caip19Id)?.assetId;
402
+ },
403
+ toCaip19(assetId) {
404
+ const token = tokens.find((entry) => entry.assetId === assetId);
405
+ if (!token)
406
+ return undefined;
407
+ const caip2Id = blockchainToCaip2.get(token.blockchain);
408
+ if (!caip2Id)
409
+ return undefined;
410
+ if (token.contractAddress) {
411
+ const namespace = Types.parseCaip2(caip2Id).namespace;
412
+ const assetNamespace = contractAssetNamespaces[namespace] ?? 'token';
413
+ return `${caip2Id}/${assetNamespace}:${token.contractAddress}`;
414
+ }
415
+ const coinType = nativeCoinTypes[token.blockchain];
416
+ if (coinType === undefined)
417
+ return undefined;
418
+ return `${caip2Id}/slip44:${coinType}`;
419
+ },
420
+ };
421
+ }
422
+ //# sourceMappingURL=OneClick.js.map