@xochi/client 0.0.0-stage → 0.1.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +119 -2
  4. package/dist/cjs/claim.d.ts +33 -0
  5. package/dist/cjs/claim.js +44 -0
  6. package/dist/cjs/fee.d.ts +247 -0
  7. package/dist/cjs/fee.js +295 -0
  8. package/dist/cjs/index.d.ts +18 -0
  9. package/dist/cjs/index.js +33 -0
  10. package/dist/cjs/machine/claimInfo.d.ts +29 -0
  11. package/dist/cjs/machine/claimInfo.js +26 -0
  12. package/dist/cjs/machine/decisions.d.ts +452 -0
  13. package/dist/cjs/machine/decisions.js +665 -0
  14. package/dist/cjs/machine/deposit.d.ts +113 -0
  15. package/dist/cjs/machine/deposit.js +265 -0
  16. package/dist/cjs/machine/index.d.ts +23 -0
  17. package/dist/cjs/machine/index.js +53 -0
  18. package/dist/cjs/machine/machine.d.ts +409 -0
  19. package/dist/cjs/machine/machine.js +1615 -0
  20. package/dist/cjs/machine/phase.d.ts +24 -0
  21. package/dist/cjs/machine/phase.js +45 -0
  22. package/dist/cjs/machine/quoteChecks.d.ts +41 -0
  23. package/dist/cjs/machine/quoteChecks.js +82 -0
  24. package/dist/cjs/machine/receipt.d.ts +151 -0
  25. package/dist/cjs/machine/receipt.js +69 -0
  26. package/dist/cjs/machine/recipientEcho.d.ts +16 -0
  27. package/dist/cjs/machine/recipientEcho.js +27 -0
  28. package/dist/cjs/machine/solverPin.d.ts +110 -0
  29. package/dist/cjs/machine/solverPin.js +214 -0
  30. package/dist/cjs/machine/state.d.ts +167 -0
  31. package/dist/cjs/machine/state.js +31 -0
  32. package/dist/cjs/machine/transition.d.ts +15 -0
  33. package/dist/cjs/machine/transition.js +20 -0
  34. package/dist/cjs/machine/validateRequest.d.ts +15 -0
  35. package/dist/cjs/machine/validateRequest.js +127 -0
  36. package/dist/cjs/machine/view.d.ts +3 -0
  37. package/dist/cjs/machine/view.js +74 -0
  38. package/dist/cjs/machine/wire.d.ts +527 -0
  39. package/dist/cjs/machine/wire.js +280 -0
  40. package/dist/cjs/package.json +1 -0
  41. package/dist/cjs/shared/api.d.ts +9 -0
  42. package/dist/cjs/shared/api.js +5 -0
  43. package/dist/cjs/shared/chains.d.ts +243 -0
  44. package/dist/cjs/shared/chains.js +250 -0
  45. package/dist/cjs/shared/eip712.d.ts +44 -0
  46. package/dist/cjs/shared/eip712.js +64 -0
  47. package/dist/cjs/shared/index.d.ts +15 -0
  48. package/dist/cjs/shared/index.js +31 -0
  49. package/dist/cjs/shared/intentStream.d.ts +74 -0
  50. package/dist/cjs/shared/intentStream.js +59 -0
  51. package/dist/cjs/shared/intents.d.ts +88 -0
  52. package/dist/cjs/shared/intents.js +227 -0
  53. package/dist/cjs/shared/schemas.d.ts +928 -0
  54. package/dist/cjs/shared/schemas.js +524 -0
  55. package/dist/cjs/shared/stealth.d.ts +53 -0
  56. package/dist/cjs/shared/stealth.js +68 -0
  57. package/dist/cjs/shared/trading.d.ts +153 -0
  58. package/dist/cjs/shared/trading.js +185 -0
  59. package/dist/cjs/shared/types.d.ts +37 -0
  60. package/dist/cjs/shared/types.js +7 -0
  61. package/dist/cjs/shared/validators.d.ts +30 -0
  62. package/dist/cjs/shared/validators.js +79 -0
  63. package/dist/cjs/transport/client.d.ts +234 -0
  64. package/dist/cjs/transport/client.js +1456 -0
  65. package/dist/esm/claim.d.ts +33 -0
  66. package/dist/esm/claim.js +41 -0
  67. package/dist/esm/fee.d.ts +247 -0
  68. package/dist/esm/fee.js +285 -0
  69. package/dist/esm/index.d.ts +18 -0
  70. package/dist/esm/index.js +17 -0
  71. package/dist/esm/machine/claimInfo.d.ts +29 -0
  72. package/dist/esm/machine/claimInfo.js +23 -0
  73. package/dist/esm/machine/decisions.d.ts +452 -0
  74. package/dist/esm/machine/decisions.js +640 -0
  75. package/dist/esm/machine/deposit.d.ts +113 -0
  76. package/dist/esm/machine/deposit.js +260 -0
  77. package/dist/esm/machine/index.d.ts +23 -0
  78. package/dist/esm/machine/index.js +23 -0
  79. package/dist/esm/machine/machine.d.ts +409 -0
  80. package/dist/esm/machine/machine.js +1609 -0
  81. package/dist/esm/machine/phase.d.ts +24 -0
  82. package/dist/esm/machine/phase.js +42 -0
  83. package/dist/esm/machine/quoteChecks.d.ts +41 -0
  84. package/dist/esm/machine/quoteChecks.js +78 -0
  85. package/dist/esm/machine/receipt.d.ts +151 -0
  86. package/dist/esm/machine/receipt.js +64 -0
  87. package/dist/esm/machine/recipientEcho.d.ts +16 -0
  88. package/dist/esm/machine/recipientEcho.js +24 -0
  89. package/dist/esm/machine/solverPin.d.ts +110 -0
  90. package/dist/esm/machine/solverPin.js +210 -0
  91. package/dist/esm/machine/state.d.ts +167 -0
  92. package/dist/esm/machine/state.js +28 -0
  93. package/dist/esm/machine/transition.d.ts +15 -0
  94. package/dist/esm/machine/transition.js +17 -0
  95. package/dist/esm/machine/validateRequest.d.ts +15 -0
  96. package/dist/esm/machine/validateRequest.js +123 -0
  97. package/dist/esm/machine/view.d.ts +3 -0
  98. package/dist/esm/machine/view.js +71 -0
  99. package/dist/esm/machine/wire.d.ts +527 -0
  100. package/dist/esm/machine/wire.js +266 -0
  101. package/dist/esm/shared/api.d.ts +9 -0
  102. package/dist/esm/shared/api.js +4 -0
  103. package/dist/esm/shared/chains.d.ts +243 -0
  104. package/dist/esm/shared/chains.js +236 -0
  105. package/dist/esm/shared/eip712.d.ts +44 -0
  106. package/dist/esm/shared/eip712.js +61 -0
  107. package/dist/esm/shared/index.d.ts +15 -0
  108. package/dist/esm/shared/index.js +15 -0
  109. package/dist/esm/shared/intentStream.d.ts +74 -0
  110. package/dist/esm/shared/intentStream.js +55 -0
  111. package/dist/esm/shared/intents.d.ts +88 -0
  112. package/dist/esm/shared/intents.js +213 -0
  113. package/dist/esm/shared/schemas.d.ts +928 -0
  114. package/dist/esm/shared/schemas.js +521 -0
  115. package/dist/esm/shared/stealth.d.ts +53 -0
  116. package/dist/esm/shared/stealth.js +64 -0
  117. package/dist/esm/shared/trading.d.ts +153 -0
  118. package/dist/esm/shared/trading.js +182 -0
  119. package/dist/esm/shared/types.d.ts +37 -0
  120. package/dist/esm/shared/types.js +6 -0
  121. package/dist/esm/shared/validators.d.ts +30 -0
  122. package/dist/esm/shared/validators.js +74 -0
  123. package/dist/esm/transport/client.d.ts +234 -0
  124. package/dist/esm/transport/client.js +1447 -0
  125. package/package.json +61 -4
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Transport for the Xochi Worker API (`<baseUrl>/api/*`).
3
+ *
4
+ * - Quote caching (TTL) and request deduplication
5
+ * - Retry with exponential backoff, closed to transient gateway statuses
6
+ * - A per-attempt timeout
7
+ * - Status polling for in-flight intents (no WebSocket)
8
+ *
9
+ * Everything host-specific is injected: the `fetch` implementation, the bearer
10
+ * token, what to do when the server says a credential is no longer good, and
11
+ * where log lines go. Nothing here reads a global config, storage or the DOM.
12
+ *
13
+ * The Worker speaks the snake_case wire contract; responses are decoded to the
14
+ * camelCase app types by the same decoders the state machine reads.
15
+ */
16
+ import type { PreparedClaim } from "@xochi/signer";
17
+ import { type ExecuteRequest, type ExecuteResponse, type IntentRecord, type WithheldIntentRecord, type IntentStatusResult, type QuoteRequest, type QuoteResponse } from "../machine/wire.js";
18
+ export interface TransportTimeouts {
19
+ /**
20
+ * Quote cache time-to-live. Must stay below the worker's 30s quote window: at
21
+ * 30s a cache hit near expiry handed back an about-to-expire quote, and the
22
+ * ~25s scheduled refresh re-hit the cache and returned the same expiring quote
23
+ * (every quote dead-ended at "expired"). 15s guarantees the refresh misses.
24
+ */
25
+ quoteCacheTtlMs: number;
26
+ /** Per-attempt timeout for quotes, status and reads. */
27
+ requestTimeoutMs: number;
28
+ /**
29
+ * Execute timeout. Execute runs synchronously through the origin pull + a
30
+ * ~25s server-side receipt confirmation + settlement, so the request timeout
31
+ * aborted it while the intent was still settling. 120s guards the slow
32
+ * cross-chain path.
33
+ */
34
+ executeTimeoutMs: number;
35
+ }
36
+ export interface RetryPolicy {
37
+ /** Total attempts for an idempotent request. */
38
+ maxAttempts: number;
39
+ /** First exponential backoff step. */
40
+ baseDelayMs: number;
41
+ /** Clamp on a server-supplied `Retry-After` used for internal pacing. */
42
+ maxDelayMs: number;
43
+ }
44
+ export declare const DEFAULT_TIMEOUTS: TransportTimeouts;
45
+ export declare const DEFAULT_RETRY: RetryPolicy;
46
+ /**
47
+ * A credential failure the server named. `expired`: a 401 whose body says the
48
+ * token expired (prompt a fresh login). `invalid`: a 401/403 carrying
49
+ * `error_category` `auth_invalid` or `wallet_auth_required` on a request that
50
+ * sent a token (drop the session so the next attempt re-authenticates). A 401
51
+ * or 403 that names neither is a business rule, not a credential failure.
52
+ */
53
+ export interface AuthExpiredEvent {
54
+ reason: "expired" | "invalid";
55
+ status: number;
56
+ errorCategory?: string;
57
+ }
58
+ export type TransportLogLevel = "debug" | "warn" | "error";
59
+ export type TransportLogger = (level: TransportLogLevel, message: string, ...data: unknown[]) => void;
60
+ export interface XochiClientOptions {
61
+ /** API origin, e.g. `https://api.xochi.fi`. Paths are `/api/...` under it. */
62
+ baseUrl: string;
63
+ /** Defaults to `globalThis.fetch`. */
64
+ fetch?: (input: string, init: RequestInit) => Promise<Response>;
65
+ /** Bearer token for authenticated calls; absent or null sends none. */
66
+ getAuthToken?: () => string | null | undefined;
67
+ /** Called when the server names a credential failure (see AuthExpiredEvent). */
68
+ onAuthExpired?: (event: AuthExpiredEvent) => void;
69
+ /** Defaults to warn/error on the console, debug dropped. */
70
+ log?: TransportLogger;
71
+ timeouts?: Partial<TransportTimeouts>;
72
+ retry?: Partial<RetryPolicy>;
73
+ }
74
+ /**
75
+ * A structured API failure. Carries the numeric HTTP status and, for
76
+ * backpressure responses (429/503), an ABSOLUTE unix-seconds retry target so the
77
+ * UI can render a countdown and disable Retry until it elapses. The message is
78
+ * `HTTP {status}: {body excerpt}` so a code-less consumer can still fall back to
79
+ * matching on it; the excerpt is capped and read-token redacted (README
80
+ * "Transport" states the rule).
81
+ *
82
+ * A failed execute may still carry the intent read capability (xochi#563: the
83
+ * server serves it on a 502 when funds may have moved). It is never a property
84
+ * of the error: take it with `takeApiErrorReadToken`.
85
+ */
86
+ export declare class ApiError extends Error {
87
+ readonly code: number;
88
+ readonly retryAfter?: number;
89
+ /**
90
+ * True only on the Worker's not-accepted answer to `execute`
91
+ * (`isNotAcceptedExecuteBody`), whose `retryAfter` is the server's wait;
92
+ * false on every other failure, which is never resent. Never true together
93
+ * with `inDoubt`: an answer that also says funds may have moved is not a
94
+ * not-accepted answer. Hand it to the machine as `ErrorReport.notAccepted`
95
+ * (rules: README "Transport").
96
+ */
97
+ readonly notAccepted: boolean;
98
+ /**
99
+ * Whether this failed `execute` answer cannot vouch that nothing moved for
100
+ * the executed intent (`isInDoubtExecuteAnswer`): false only on the three
101
+ * Worker answers that speak for the intent itself, true on every other
102
+ * execute failure, a refusal of this request alone included (another
103
+ * request may hold the intent). For the host's own copy and logs; the
104
+ * machine never reads it (README "Transport"). False on every other call.
105
+ */
106
+ readonly inDoubt: boolean;
107
+ constructor(message: string, code: number, retryAfter?: number, notAccepted?: boolean, inDoubt?: boolean);
108
+ }
109
+ /**
110
+ * Take the `read_token` of the intent a failed `execute` named, once: a second
111
+ * call returns undefined. The error is an `ApiError` (a 502 when funds may have
112
+ * moved) or the `Invalid response format` error of a 2xx execute body whose
113
+ * other fields broke the contract. Keep it with the intent and poll with it,
114
+ * exactly as for a successful execute; take it before the error goes to any
115
+ * reporter. Undefined for any other error, and for a token naming another
116
+ * intent.
117
+ */
118
+ export declare function takeApiErrorReadToken(error: unknown): string | undefined;
119
+ /**
120
+ * Parse a Retry-After header (our API sends integer seconds) into milliseconds,
121
+ * clamped to [0, maxDelayMs] so a malformed or hostile value can't stall the
122
+ * client. Returns null when absent/unparseable so the caller falls back to its
123
+ * own exponential backoff.
124
+ */
125
+ export declare function parseRetryAfterMs(headerValue: string | null, maxDelayMs?: number): number | null;
126
+ /**
127
+ * Ceiling on the DERIVED display wait (seconds). Distinct from the internal
128
+ * backoff clamp: this bounds only the user-facing countdown so a
129
+ * buggy-but-trusted upstream value (e.g. an absolute epoch mistakenly sent in a
130
+ * delta-only Retry-After header) cannot produce a multi-decade countdown that
131
+ * disables Retry until a reload. 10 minutes is far past any legitimate
132
+ * backpressure window.
133
+ */
134
+ export declare const MAX_RETRY_WAIT_SEC = 600;
135
+ /**
136
+ * Resolve the display countdown target (ABSOLUTE unix SECONDS) for a
137
+ * backpressure response. Distinct from parseRetryAfterMs, which returns clamped
138
+ * milliseconds for internal retry pacing: a display countdown reflects the full
139
+ * server-requested wait, bounded only by MAX_RETRY_WAIT_SEC.
140
+ *
141
+ * Sources, in priority order:
142
+ * - `Retry-After` header: DELTA seconds, converted to absolute (now + delta).
143
+ * - `X-RateLimit-Reset` header: already ABSOLUTE unix seconds.
144
+ * - `retryAfter` in a JSON body (429 response): already ABSOLUTE unix seconds.
145
+ *
146
+ * Returns undefined when no source is present or parseable (so no countdown is
147
+ * shown and Retry stays enabled).
148
+ */
149
+ export declare function resolveRetryAfterSeconds(headers: Headers, body: string): number | undefined;
150
+ /**
151
+ * `POST /api/stealth/claim/prepare` body: the sponsored UserOperation, its hash
152
+ * and the EntryPoint/chain it was hashed under. Verify it with
153
+ * `checkPreparedClaim` before signing `userOpHash`.
154
+ */
155
+ export interface PreparedStealthClaim extends PreparedClaim {
156
+ success: boolean;
157
+ }
158
+ /** `POST /api/stealth/claim/submit` 200 body. */
159
+ export interface SubmittedStealthClaim {
160
+ success: boolean;
161
+ txHash?: string;
162
+ opHash?: string;
163
+ blockNumber?: number;
164
+ }
165
+ /**
166
+ * A single-attempt call's outcome. `errorCategory` is the server's stable key
167
+ * when it named one (`claim_pending` on a 503 means the op was accepted and not
168
+ * mined yet: resubmit the SAME body to poll it, never prepare or sign again).
169
+ */
170
+ export type CallResult<T> = {
171
+ ok: true;
172
+ data: T;
173
+ } | {
174
+ ok: false;
175
+ error: string;
176
+ status?: number;
177
+ errorCategory?: string;
178
+ body?: unknown;
179
+ };
180
+ /**
181
+ * Options for the per-intent reads. `readToken` is the `read_token` the execute
182
+ * of this intent returned (`ExecuteResponse.readToken`, xochi#563); it is sent
183
+ * as the `X-Xochi-Read-Token` header so a stealth intent reads with its
184
+ * on-chain legs (xochi#565, merged as `0317e84`, once deployed). Absent, the
185
+ * read is anonymous and those legs are omitted; the server answers a wrong
186
+ * token exactly like an absent one. A token read of a shielded intent gets
187
+ * only the status-only view, as an anonymous read does (`legsWithheld`: no
188
+ * Aztec identifiers, tx hashes, tokens, amounts, fees or timestamps;
189
+ * `substatus` only when it is `not_executed`); only a SIWE session for the
190
+ * intent's wallet reads its full record (SPEC.md §5.1).
191
+ */
192
+ export interface IntentReadOptions {
193
+ readToken?: string;
194
+ }
195
+ /**
196
+ * An intent read or an `execute` answered by a redirect it may not follow, or
197
+ * by a response from another origin or naming none (rules: README
198
+ * "Transport"). `getIntent`, `getIntentHistory` and `execute` reject with it
199
+ * (an execute refused this way is in doubt); `getIntentStatus` reports it once
200
+ * per intent and reads again without the token, or returns null when that
201
+ * read is refused too.
202
+ */
203
+ export declare class RedirectRefused extends Error {
204
+ constructor(path: string, reason?: string);
205
+ }
206
+ export interface XochiClient {
207
+ /** Request a quote. Cached for the TTL (pull-bearing quotes never) and deduplicated. */
208
+ getQuote(request: QuoteRequest, opts?: {
209
+ forceRefresh?: boolean;
210
+ }): Promise<QuoteResponse>;
211
+ invalidateQuote(request: QuoteRequest): void;
212
+ clearQuoteCache(): void;
213
+ /** Execute a signed intent. Single attempt: a failure is in doubt, resolve it by polling status. */
214
+ execute(request: ExecuteRequest): Promise<ExecuteResponse>;
215
+ /**
216
+ * One intent record, or null on a 404. A stealth intent read without its
217
+ * `readToken`, and a shielded one read without an owner session (token or
218
+ * not: `IntentReadOptions`), is the status-only
219
+ * `WithheldIntentRecord` (`legsWithheld: true`); narrow on it before reading
220
+ * any traded value.
221
+ */
222
+ getIntent(intentId: string, opts?: IntentReadOptions): Promise<IntentRecord | WithheldIntentRecord | null>;
223
+ getIntentStatus(intentId: string, opts?: IntentReadOptions): Promise<IntentStatusResult>;
224
+ getIntentHistory(wallet: string, limit?: number): Promise<IntentRecord[]>;
225
+ prepareStealthClaim(body: {
226
+ counterfactualAddress: string;
227
+ recipientAddress: string;
228
+ }): Promise<CallResult<PreparedStealthClaim>>;
229
+ submitStealthClaim(body: {
230
+ userOpHash: string;
231
+ signature: string;
232
+ }): Promise<CallResult<SubmittedStealthClaim>>;
233
+ }
234
+ export declare function createXochiClient(options: XochiClientOptions): XochiClient;