@coinlist-co/react 0.12.1 → 0.12.2-rc.0ff2d03

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 (34) hide show
  1. package/README.md +10 -0
  2. package/dist/chunk-MJ6YXZWH.js +22 -0
  3. package/dist/chunk-MJ6YXZWH.js.map +1 -0
  4. package/dist/{chunk-VH34YC3Q.js → chunk-PWGUKS4Q.js} +199 -186
  5. package/dist/chunk-PWGUKS4Q.js.map +1 -0
  6. package/dist/{chunk-DCW7NQXH.js → chunk-QTBEDN7C.js} +52 -19
  7. package/dist/chunk-QTBEDN7C.js.map +1 -0
  8. package/dist/{chunk-MOE5S6CE.js → chunk-SVPFFLIT.js} +1314 -796
  9. package/dist/chunk-SVPFFLIT.js.map +1 -0
  10. package/dist/client/index.d.ts +3332 -4554
  11. package/dist/client/index.js +7374 -4387
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/server/index.d.ts +69 -71
  14. package/dist/server/index.js +327 -67
  15. package/dist/server/index.js.map +1 -1
  16. package/dist/{tokens-namespace-CIWBtgt2.d.cts → time-BDzqB806.d.ts} +1901 -2491
  17. package/dist/{token-registry-snapshot-CqI6dXM_.d.cts → token-registry-snapshot-cvLsnMo_.d.ts} +74 -42
  18. package/dist/universal/index.d.ts +100 -1001
  19. package/dist/universal/index.js +52 -36
  20. package/package.json +18 -14
  21. package/dist/chunk-DCW7NQXH.js.map +0 -1
  22. package/dist/chunk-MOE5S6CE.js.map +0 -1
  23. package/dist/chunk-VH34YC3Q.js.map +0 -1
  24. package/dist/client/index.cjs +0 -16840
  25. package/dist/client/index.cjs.map +0 -1
  26. package/dist/client/index.d.cts +0 -6592
  27. package/dist/server/index.cjs +0 -3878
  28. package/dist/server/index.cjs.map +0 -1
  29. package/dist/server/index.d.cts +0 -354
  30. package/dist/token-registry-snapshot-BNBNuDu1.d.ts +0 -179
  31. package/dist/tokens-namespace-CIWBtgt2.d.ts +0 -3301
  32. package/dist/universal/index.cjs +0 -4028
  33. package/dist/universal/index.cjs.map +0 -1
  34. package/dist/universal/index.d.cts +0 -1276
@@ -1,4 +1,4 @@
1
- import { Hash, Transport, PublicClient, Hex } from 'viem';
1
+ import { Hash, Hex, Transport } from 'viem';
2
2
 
3
3
  declare const __brand: unique symbol;
4
4
  type Newtype<Base, Branding> = Base & {
@@ -78,1623 +78,930 @@ type RedactedWalletError = Exclude<WalletError, {
78
78
  }> | {
79
79
  type: 'unknown';
80
80
  };
81
- declare const RedactedWalletError: {
82
- fromWalletError: (error: WalletError) => RedactedWalletError;
83
- };
84
81
 
85
82
  /**
86
- * The observability seam a host implements to see inside the SDK.
87
- *
88
- * Pass an implementation as {@link Config.logger} and the SDK reports every
89
- * request it makes, every failure it classifies, and every state a hook
90
- * settles into. Leave it out and the SDK logs nothing at all - there is no
91
- * fallback to `console`, at any level, on any codepath.
92
- *
93
- * Four methods over a {@link LogEvent} lambda, plus {@link level}. An event is
94
- * a constant `msg`, the {@link LogScope} it came from, the accumulated
95
- * {@link LogBindings}, and a bag of `fields` - so a host forwards structured
96
- * records rather than parsing strings.
97
- *
98
- * ```ts
99
- * const logger: Logger = {
100
- * level: () => 'info',
101
- * debug: () => {},
102
- * info: (event) => sink.info(toRecord(event())),
103
- * warn: (event) => sink.warn(toRecord(event())),
104
- * error: (event) => sink.error(toRecord(event())),
105
- * };
106
- * ```
107
- *
108
- * {@link pinoClientLogger} and {@link pinoServerLogger} are ready-made
109
- * implementations over pino, and are what most hosts should reach for.
110
- *
111
- * ## Where to run it
112
- *
113
- * **Not in production, as the default answer.** Development, tests, staging,
114
- * and reproducing a reported bug are where this seam earns its keep. Left
115
- * undefined elsewhere it discloses nothing, and that holds no matter what a
116
- * later revision of the SDK puts on a line.
117
- *
118
- * Where you do run one in production, `'info'` and above is the only sane
119
- * choice, and the shipped implementations make the alternative unrepresentable
120
- * rather than merely discouraged: `isDev: false` narrows the level to
121
- * {@link ProductionLogLevel}, so `'debug'` does not compile. That narrowing is
122
- * a guard for the partner who ignored this paragraph, not an endorsement of
123
- * the practice.
124
- *
125
- * The distinction worth holding onto: `'info'` and above are **redacted by
126
- * construction** - see the next section for exactly what that covers, and the
127
- * type that enforces it. Redacted is a mechanism the SDK can hold itself to. A
128
- * blanket "safe in production" is a promise about your environment that the
129
- * SDK is in no position to make, and one that a single future field could
130
- * quietly break.
131
- *
132
- * `'debug'` is for reproducing a bug: a developer's machine, tests, and beta
133
- * or staging environments where the data flowing through the SDK is not real
134
- * customer data.
135
- *
136
- * ## What each level may disclose
137
- *
138
- * **`'debug'` is unredacted. Every other level is not.**
139
- *
140
- * At `'debug'` the SDK reports request and response bodies, request headers,
141
- * the full URL including its query string, and operation parameters
142
- * **verbatim** - bearer tokens, KYC answers, tax-document fields, wallet
143
- * signatures, the lot. There is no denylist and no redaction: see ADR-8 for
144
- * why a partial guarantee was rejected in favour of an honest absence of one.
145
- *
146
- * At `'info'`, `'warn'` and `'error'` the SDK emits only its own
147
- * classification of what happened and identifiers the server minted: a method
148
- * and a bare path, a status, a duration, a {@link RequestId}, a frontline
149
- * error code and event id, a {@link LogCause} arm. No request body, no
150
- * response body, no query string, no host-supplied parameters, and no message
151
- * text read off a thrown error the SDK did not author.
152
- *
153
- * The type carries that boundary rather than merely documenting it:
154
- * {@link SafeEvent} constrains every field value to a {@link LogValue} scalar,
155
- * so spreading a body, a DTO or an operation's params into an `info`, `warn`
156
- * or `error` line does not compile. Only {@link DebugEvent} accepts arbitrary
157
- * values.
158
- *
159
- * The one deliberate exception is an SDK-authored validation message, which
160
- * quotes the single scalar that failed to map - `Unsupported chain:
161
- * "solana_mainnet"`. It is a hand-written template naming one offending value,
162
- * not a payload, and it is the whole reason this seam exists.
163
- *
164
- * ## Your responsibility
165
- *
166
- * The SDK hands you events. What happens next is yours: **you own the final
167
- * filtering, redaction and retention** before anything is written to a file, a
168
- * console, or a third-party sink such as Sentry or Datadog. In particular, if
169
- * you implement {@link debug} and forward it anywhere durable, you have taken
170
- * on storing live credentials and personal data, deliberately. Implement
171
- * `debug` as a no-op - or return a level below it from {@link level} - in any
172
- * environment where that is not acceptable.
173
- *
174
- * **Every event is a lambda, and that is load bearing.** The SDK consults
175
- * {@link level} first and never invokes the lambda when the level forbids it,
176
- * so an expensive debug event - a serialized DTO, a loop over balances - costs
177
- * nothing in production where the level is `'error'` or `'none'`. Build the
178
- * event inside the lambda, never outside it.
179
- *
180
- * ## Every method must be total
181
- *
182
- * **None of the five methods may throw, and that includes {@link level}.** The
183
- * SDK calls them inline, on the codepath of the work it is reporting, and it
184
- * does not catch them: a `logger.error` that throws while reporting a failed
185
- * request turns that request's rejection into your logger's exception, and a
186
- * `level()` that throws breaks the call it was asked about even when nothing
187
- * else went wrong.
188
- *
189
- * That is deliberate. Swallowing would hide a logger that is broken in every
190
- * environment, and the SDK cannot tell a transport blip in your sink from a
191
- * bug in your implementation. So the contract is on this side of the seam:
192
- * catch inside your implementation, and never let an exception out. The
193
- * shipped pino implementations do exactly that.
194
- *
195
- * ```ts
196
- * error: (event) => {
197
- * try {
198
- * const e = event();
199
- * Sentry.captureMessage(e.msg, { extra: e });
200
- * } catch {
201
- * // never rethrow: the SDK is on the other side of this call
202
- * }
203
- * },
204
- * ```
83
+ * The exhaustive chain set: a new {@link EthereumChain} is a compile error
84
+ * here until it is listed, which is what keeps the constructor total.
205
85
  */
206
- interface Logger {
207
- /**
208
- * The most detailed level this logger wants. Consulted before **every** log
209
- * call, so keep it cheap and pure: return a captured constant or read a
210
- * plain field. Do not read an environment variable, hit storage, or call
211
- * out to a feature-flag service here.
212
- *
213
- * Returning `'none'` silences the SDK as completely as omitting the logger.
214
- *
215
- * **Must not throw.** It is consulted inside every SDK operation, so an
216
- * exception here fails the operation itself.
217
- */
218
- level(): LogLevel;
219
- /**
220
- * Fine-grained detail: request and response bodies, full URLs, headers,
221
- * operation parameters, hook transitions.
222
- *
223
- * **Unredacted.** This is the only level that may carry credentials or
224
- * personal data, and it always may. Its event is a {@link DebugEvent}, whose
225
- * fields accept arbitrary values for exactly that reason. Do not forward it
226
- * to a durable or third-party sink outside development and beta.
227
- *
228
- * **Must not throw**, here and on every method below.
229
- */
230
- debug(event: () => DebugEvent): void;
231
- /** Notable, expected events: a request completing, a flow advancing. */
232
- info(event: () => SafeEvent): void;
233
- /**
234
- * Recoverable trouble: a retried request, a renewed session, a poll tick
235
- * that failed while the last value still stands.
236
- *
237
- * Carries a {@link LogCause} when one was classified, the same as
238
- * {@link error} does. What separates the two levels is whether anything
239
- * actually broke, not how much is known about it.
240
- */
241
- warn(event: () => SafeEvent): void;
242
- /**
243
- * A failure. {@link SafeEvent.cause} carries what the SDK knows about it,
244
- * classified into {@link LogCause}, and is absent when the failure is not an
245
- * error the SDK caught - a flow reporting the step it stopped at, for
246
- * instance.
247
- *
248
- * Every part of the event is SDK-authored classification or server-authored
249
- * identifiers, so forwarding it as it stands is safe. `scope` says which
250
- * part of the SDK the line came from, which is what to route or filter on.
251
- */
252
- error(event: () => SafeEvent): void;
253
- }
86
+ declare const ETHEREUM_CHAINS: Record<EthereumChain, true>;
87
+ type EthereumChain = 'ethereum_mainnet' | 'ethereum_sepolia' | 'base_mainnet' | 'base_sepolia';
254
88
  /**
255
- * A value the SDK is willing to put in a structured field above `'debug'`.
256
- *
257
- * Scalars only, and that is the redaction boundary made structural rather than
258
- * documentary. Every leak ADR-8 found was an *object* spread onto a line that
259
- * a host forwards to a tracker - an operation's params, a thrown wallet error,
260
- * a response body. None of them is assignable to this, so each is now a
261
- * compile error rather than a review catch.
89
+ * Validates that a raw backend string names a chain the SDK supports.
262
90
  *
263
- * It does not stop a call site writing `{ token: accessToken }`, and it is not
264
- * meant to: a string is a string. What it stops is the mistake that actually
265
- * happened.
91
+ * @throws ValidationError on anything else. Passing an unknown chain through
92
+ * would let downstream EIP-155 lookups blow up far from the response that
93
+ * caused it.
266
94
  */
267
- type LogValue = string | number | boolean | null;
95
+ declare const EthereumChain: (value: string) => EthereumChain;
268
96
  /**
269
- * Fields safe at every level: SDK-authored classification and server-minted
270
- * identifiers, constrained to {@link LogValue}.
97
+ * The exhaustive chain set: a new {@link SolanaChain} is a compile error
98
+ * here until it is listed, which is what keeps the constructor total.
271
99
  */
272
- type SafeFields = Readonly<Record<string, LogValue>>;
100
+ declare const SOLANA_CHAINS: Record<SolanaChain, true>;
101
+ type SolanaChain = 'solana_mainnet' | 'solana_devnet';
273
102
  /**
274
- * Fields for `'debug'` only: bodies, headers, full URLs, operation params,
275
- * raw thrown values. Arbitrary values, because that is the point of the level.
103
+ * Validates that a raw backend string names a chain the SDK supports.
276
104
  *
277
- * The SDK's own implementations render these into a form that cannot fail to
278
- * serialize - a `bigint`, a cycle or a getter that throws must never turn a
279
- * log line into a second failure - but they are otherwise passed through.
105
+ * @throws ValidationError on anything else. Passing an unknown chain through
106
+ * would let downstream EIP-155 lookups blow up far from the response that
107
+ * caused it.
280
108
  */
281
- type UnredactedFields = Readonly<Record<string, unknown>>;
109
+ declare const SolanaChain: (value: string) => SolanaChain;
282
110
  /**
283
- * One narrowing applied to a logger by {@link InternalLogger.child}.
284
- *
285
- * A union of single-key objects rather than a positional `string`, because the
286
- * three things the SDK narrows by are not the same kind of thing: a request id
287
- * correlates retries of one wire call, a flow name names an on-chain
288
- * procedure, a hook name names a React binding. Rendered into one `[SCOPE:x]`
289
- * prefix they were indistinguishable; as fields they must not be.
290
- *
291
- * `requestId` keeps its {@link RequestId} newtype rather than widening to
292
- * `string` at the seam.
111
+ * {@link Chain}'s check without the throw, for a mapper over a list that drops
112
+ * the entries it cannot name rather than rejecting the whole list. Frontline's
113
+ * chain registry may carry chains this SDK does not model yet.
293
114
  */
294
- type LogBinding = {
295
- readonly requestId: RequestId;
296
- } | {
297
- readonly flow: string;
298
- } | {
299
- readonly hook: string;
300
- } | {
301
- readonly op: string;
302
- };
115
+ declare const isChain: (value: string) => value is Chain;
116
+ type Chain = EthereumChain | SolanaChain;
117
+ declare const Chain: (value: string) => Chain;
303
118
  /**
304
- * Every {@link LogBinding} applied so far, merged. This is what reaches a
305
- * host, so a line from `prepareOndoSell` carries `flow: 'prepareSell'` as its
306
- * own key and is filterable without parsing anything.
119
+ * Protocol a wallet binding is scoped to. EVM-only for now: an EVM address
120
+ * binds once per option regardless of which EVM chain proved ownership.
121
+ * Frontline's enum also has `:solana`, but we don't handle Solana bindings yet,
122
+ * so this stays `'ethereum'` until Solana support lands.
307
123
  */
308
- type LogBindings = {
309
- readonly [K in LogBinding as keyof K]?: K[keyof K];
310
- };
311
- type BaseEvent = {
312
- /**
313
- * What happened, as a **constant string literal**.
314
- *
315
- * Never interpolated. A log aggregator groups by message, so a `msg` that
316
- * embeds a status, a count or a reason produces one group per distinct
317
- * value and is not groupable at all. Every varying value belongs in
318
- * `fields`, which is what a host filters and facets on.
319
- *
320
- * This is a convention, not a checked rule: deslop reasons about import
321
- * edges and cannot see a template literal, and the type-level approximations
322
- * catch only some interpolations. ADR-8 records why a partial guarantee was
323
- * refused here as elsewhere.
324
- */
325
- readonly msg: string;
326
- /**
327
- * Which part of the SDK reported this. Stamped by
328
- * {@link internalLogger}, never by a call site.
329
- */
330
- readonly scope: LogScope;
331
- /** Every {@link LogBinding} applied by {@link InternalLogger.child}. */
332
- readonly bindings: LogBindings;
333
- };
124
+ type WalletProtocol = 'ethereum';
334
125
  /**
335
- * An event for `'info'`, `'warn'` and `'error'` - the levels a host may run in
336
- * production.
337
- *
338
- * Its fields are {@link SafeFields}, so nothing but a scalar can reach it.
126
+ * EVM addresses keep a `0x${string}` base so they stay assignable to the
127
+ * `0x${string}` shapes that on-chain libraries (viem/wagmi) expect. We only
128
+ * drop the runtime `0x` narrowing: values are trusted at the boundary and
129
+ * branded via the constructor.
339
130
  */
340
- type SafeEvent = BaseEvent & {
341
- readonly fields: SafeFields;
131
+ type EvmWalletAddress = Newtype<`0x${string}`, 'EvmWalletAddress'>;
132
+ declare const EvmWalletAddress: (value: string) => EvmWalletAddress;
133
+ type EvmContractAddress = Newtype<`0x${string}`, 'EvmContractAddress'>;
134
+ declare const EvmContractAddress: (value: string) => EvmContractAddress;
135
+ /**
136
+ * What uniquely identifies a token: the chain it is deployed on plus its
137
+ * contract address. Never a symbol, which can collide across issuers.
138
+ */
139
+ type TokenIdentifier = {
140
+ /** The chain the token contract is deployed on. */
141
+ chain: EthereumChain;
342
142
  /**
343
- * What the SDK knows about an accompanying failure, classified. Absent when
344
- * there is nothing to classify.
143
+ * The token's contract address, in any casing — consumers that need the
144
+ * EIP-55 form (e.g. the token registry routes) checksum it themselves.
345
145
  */
346
- readonly cause?: LogCause;
146
+ address: EvmContractAddress;
347
147
  };
148
+ type HexEncodedTransactionData = Newtype<`0x${string}`, 'HexEncodedTransactionData'>;
149
+ declare const HexEncodedTransactionData: (value: string) => HexEncodedTransactionData;
348
150
  /**
349
- * An event for `'debug'`, the unredacted level. Its fields accept anything.
151
+ * A transaction the backend has already encoded for us — the caller only
152
+ * broadcasts it. Used by the flows that hand a wallet a ready-made `to`/`data`
153
+ * pair (allow-listing a wallet, an Ondo swap) rather than encoding a contract
154
+ * call themselves.
350
155
  */
351
- type DebugEvent = BaseEvent & {
352
- readonly fields: UnredactedFields;
156
+ type Tx = {
157
+ to: EvmContractAddress;
158
+ data: HexEncodedTransactionData;
353
159
  };
354
160
  /**
355
- * How much the SDK may report, from silent to everything.
356
- *
357
- * Ordered: each level admits itself and everything before it, so `'info'`
358
- * shows errors, warnings and info but not debug. `'none'` admits nothing.
161
+ * The largest exponent whose power of ten still fits a uint256: 10^77 fits,
162
+ * 10^78 does not.
359
163
  *
360
- * `'debug'` is the unredacted level, and it is deliberately the bottom of the
361
- * ladder: the guarantee "`debug` is unredacted and every other level is not"
362
- * only closes if there is nothing below it. That is why the SDK keeps its own
363
- * five levels rather than adopting pino's seven, which put `trace` underneath.
164
+ * The bound is arithmetic rather than a token standard's. ERC-20 reports
165
+ * `decimals()` as a `uint8`, so 255 is what a contract *can* say, but every
166
+ * {@link AssetDecimals} in this codebase is paired with a {@link Uint256} raw
167
+ * by construction, and scaling by anything past this leaves that pairing
168
+ * unrepresentable. A value between 78 and 255 would therefore pass a
169
+ * standard-faithful check and then break the first multiplication it reached.
364
170
  */
365
- type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug';
171
+ declare const MAX_ASSET_DECIMALS = 77;
366
172
  /**
367
- * The redacted levels: the only ones to consider running in production.
368
- *
369
- * Every level except `'debug'`, which is unredacted by design. Used by the
370
- * shipped pino implementations to make a production `'debug'` logger
371
- * unrepresentable rather than merely discouraged - see
372
- * {@link PinoLoggerOptions}.
173
+ * How many decimal places one whole token divides into.
373
174
  *
374
- * The name marks which levels are *redacted*, not a verdict that running them
375
- * in production is safe. The SDK's advice is to leave {@link Config.logger}
376
- * undefined there; this type is what stops the partner who does not take it
377
- * from reaching the unredacted level as well.
378
- */
379
- type ProductionLogLevel = Exclude<LogLevel, 'debug'>;
380
- /**
381
- * How to build one of the SDK's pino-backed loggers.
175
+ * Validated, unlike its neighbours here — {@link Uint256} is a bare `bigint`
176
+ * with a separate {@link assertUint256} guard, and {@link DecimalString} casts
177
+ * unchecked. The asymmetry is deliberate and follows the value's provenance:
178
+ * an `AssetDecimals` used to come only from literals and the token registry,
179
+ * but backends now publish exponents of their own (`asset_decimals` on an Ondo
180
+ * quote, both scales on a built swap), and those feed `10n ** BigInt(d)`
181
+ * during render. Guarding at construction is what keeps a fractional or
182
+ * unbounded exponent from surfacing as a `RangeError` mid-render — or, at
183
+ * 10^200, as a hung tab — instead of as the failed state the screen has.
382
184
  *
383
- * A union rather than `{ level, isDev }`, and that is the whole point:
384
- * `{ isDev: false, level: 'debug' }` does not typecheck, so a production
385
- * build cannot ship a logger that prints bearer tokens to a browser console.
185
+ * See docs/ADR-7-VALIDATED-NEWTYPES.md.
386
186
  *
387
- * `isDev` does exactly one thing - decide whether `'debug'` is a legal level.
388
- * It changes no formatting: dev output and production output are the same
389
- * shape, so what you debug is what you ship. Pretty-printing is a pipe the
390
- * host owns (`node server.js | npx pino-pretty`), not a dependency the SDK
391
- * takes.
187
+ * @throws ValidationError on a non-integer, a negative, or anything above
188
+ * {@link MAX_ASSET_DECIMALS}.
392
189
  */
393
- type PinoLoggerOptions = {
394
- readonly isDev: true;
395
- readonly level: LogLevel;
396
- } | {
397
- readonly isDev: false;
398
- readonly level: ProductionLogLevel;
399
- };
190
+ type AssetDecimals = Newtype<number, 'AssetDecimals'>;
191
+ declare const AssetDecimals: (value: number) => AssetDecimals;
192
+ declare const STABLE_DECIMALS: AssetDecimals;
400
193
  /**
401
- * Which part of the SDK a line came from, carried as the `scope` field on
402
- * every event.
194
+ * A non-negative decimal number the backend sent as a string, kept unscaled.
403
195
  *
404
- * It names the seam that **reported** the line, not the layer that raised what
405
- * it reports: an error from `OFFERS` surfaced by `useOffers` is reported on a
406
- * `HOOKS` line, because that is the hook telling you what it did with it. The
407
- * `OFFERS` line for the same error is emitted separately, by the namespace.
196
+ * Reach for it when a response gives you a number but not the decimals to
197
+ * scale it by, or gives you one it has already applied. Inventing an exponent
198
+ * for either is how a display ends up orders of magnitude out.
408
199
  *
409
- * A closed union rather than a free string so that `ONDO` and `Ondo` cannot
410
- * both appear, so that adding a namespace without giving it a scope fails to
411
- * compile, and so that a host can `switch` on it when routing. Anything finer
412
- * - an operation, a flow, a hook - is a {@link LogBinding} and appears as its
413
- * own field.
414
- */
415
- type LogScope = 'HTTP' | 'RPC' | 'AUTH' | 'OFFERS' | 'REQUIREMENTS' | 'WALLETS' | 'ERC20' | 'TOKEN_SALE' | 'SUPERSTATE' | 'ONDO' | 'TOKENS' | 'SUPPORT' | 'HOOKS';
416
- /**
417
- * Correlates every line one logical request produces. The first attempt, each
418
- * retry, and the re-send after a session renewal all carry the same id, so a
419
- * failure can be read back to the request that caused it.
200
+ * Contrast {@link BlockchainAmount}, which pairs a raw uint256 with the
201
+ * decimals it is denominated in and so is only constructible when you know
202
+ * both. Arithmetic on a `DecimalString` needs a decimal library we
203
+ * deliberately do not ship.
420
204
  */
421
- type RequestId = Newtype<string, 'RequestId'>;
205
+ type DecimalString = Newtype<string, 'DecimalString'>;
206
+ declare const DecimalString: (value: string) => DecimalString;
207
+ declare const MAX_UINT_256: bigint;
422
208
  /**
423
- * The id frontline keys its own logs by, minted server-side and returned as
424
- * `event_id` on an error envelope. Quote it when raising a ticket with
425
- * CoinList support: it is what leads from a partner's log line to the request
426
- * as the backend saw it.
427
- *
428
- * A {@link Newtype} rather than a bare `string` because it sits next to
429
- * {@link RequestId} and frontline's `code` on the same cause, and the three
430
- * are not interchangeable: an id swapped for a code reads plausibly and points
431
- * a support engineer at nothing.
209
+ * A non-negative integer within uint256 bounds. Kept unbranded (a plain
210
+ * `bigint`) so raw on-chain amounts flow in without ceremony; bounds are
211
+ * enforced where it matters (see {@link combineAmounts}).
432
212
  */
433
- type FrontlineEventId = Newtype<string, 'FrontlineEventId'>;
213
+ type Uint256 = bigint;
434
214
  /**
435
- * What the SDK knows about a failure, classified.
436
- *
437
- * **Safe to forward as it stands.** Every arm is SDK-authored classification
438
- * or a server-authored identifier: no request or response body, no host-
439
- * supplied parameter, and no value read off a thrown error the SDK did not
440
- * author. There is nothing here to strip before handing it to an error
441
- * tracker, which is deliberate - a `LogCause` reaches {@link Logger.error},
442
- * the level a host actually runs in production, and a guarantee that depends
443
- * on the host remembering to strip a field is not a guarantee.
444
- *
445
- * It is the SDK's **only** structured classification of a thrown error, and it
446
- * reaches {@link Logger.warn} as well as {@link Logger.error}: a recoverable
447
- * failure is no less worth naming than a fatal one, and rendering it into a
448
- * string instead would have meant interpolating into `msg`.
449
- *
450
- * Bodies are a `'debug'`-only disclosure and stay on the `HTTP` lines, which
451
- * {@link RequestId} points at.
452
- *
453
- * The arms are the remedies, not the exception classes: a `validation` cause
454
- * means the backend sent something the SDK does not understand and is worth
455
- * reporting to CoinList, while an `invariant` cause means the SDK itself
456
- * computed something impossible and is worth reporting as an SDK bug. Reading
457
- * `type` should tell you who has to fix it.
215
+ * Asserts a computed bigint falls within uint256 bounds. Use at on-chain
216
+ * arithmetic boundaries (bps math, price computation) where the SDK's own
217
+ * arithmetic could underflow below zero or overflow above 2^256-1.
458
218
  *
459
- * It carries no scope of its own. The {@link SafeEvent} it arrives on has one,
460
- * and a cause could only ever have repeated it: the scope is stamped by the
461
- * logger that *emits* the line, not by the layer that raised the error, so a
462
- * `validation` failure from `OFFERS` surfaced by `useOffers` would have been
463
- * tagged `HOOKS` on the hook's line. Forward the event, not the cause alone.
464
- */
465
- /**
466
- * Why an on-chain read failed, named after what a caller would do about it
467
- * rather than after the viem error class that reported it. See the `rpc` arm
468
- * of {@link LogCause}.
219
+ * Throws {@link InvariantError}, because reaching it means the SDK computed
220
+ * something no chain could represent. **For a value that came off the wire,
221
+ * use {@link parseUint256} instead**: the backend sending an unrepresentable
222
+ * number is a {@link ValidationError}, and the two have different remedies.
469
223
  */
470
- type RpcFailureReason = 'reverted' | 'node-rejected' | 'transport' | 'malformed-response' | 'unknown';
471
- type LogCause =
224
+ declare const assertUint256: (value: bigint) => Uint256;
472
225
  /**
473
- * A request reached the backend and came back non-2xx.
226
+ * {@link assertUint256} at a wire boundary: the same bounds check, reported as
227
+ * a {@link ValidationError} against the named field, because a value out of
228
+ * range here is the backend's, not the SDK's.
474
229
  *
475
- * Deliberately carries no response body. `requestId` is how you get the
476
- * rest: the `HTTP` lines for the same request carry the method, the path,
477
- * the duration, every retry, and at `'debug'` the body itself. `code` and
478
- * `eventId` are frontline's own - quote `eventId` when raising a ticket with
479
- * CoinList support.
230
+ * `label` names the field the way a DTO mapper's other failures do, so a log
231
+ * line says which one - `SwapPreview.pay_input_amount`, not `a number`.
480
232
  */
481
- {
482
- type: 'http';
483
- requestId: RequestId | null;
484
- status: number;
485
- code: string | null;
486
- eventId: FrontlineEventId | null;
487
- }
233
+ declare const parseUint256: (value: bigint, label: string) => Uint256;
234
+ type BlockchainAmount = Newtype<{
235
+ raw: Uint256;
236
+ decimals: AssetDecimals;
237
+ }, 'BlockchainAmount'>;
488
238
  /**
489
- * The wire succeeded; the payload was not a shape the SDK can map.
490
- *
491
- * `message` is an SDK-authored template and may quote the single scalar that
492
- * failed - `Unsupported chain: "solana_mainnet"`, `expires_at: not a date
493
- * ("13/40/2026")`. That one value is the point of the arm; it is never a
494
- * body and never a whole field set.
239
+ * Constructs a {@link BlockchainAmount} and exposes arithmetic helpers.
240
+ * TypeScript has no operator overloading, so use `BlockchainAmount.add(a, b)`
241
+ * instead of `+`/`-`/`*`/`/` on the objects directly.
495
242
  */
496
- | {
497
- type: 'validation';
498
- message: string;
499
- }
243
+ declare const BlockchainAmount: ((value: {
244
+ raw: Uint256;
245
+ decimals: AssetDecimals;
246
+ }) => BlockchainAmount) & {
247
+ add: (a: BlockchainAmount, b: BlockchainAmount) => BlockchainAmount;
248
+ sub: (a: BlockchainAmount, b: BlockchainAmount) => BlockchainAmount;
249
+ /**
250
+ * `a * b`, in `a`'s decimals - a price times a quantity. Multiplies before
251
+ * it rescales, then truncates towards zero. The exact inverse of `div`.
252
+ */
253
+ mul: (a: BlockchainAmount, b: BlockchainAmount) => BlockchainAmount;
254
+ /**
255
+ * `a / b`, in `a`'s decimals - a price, a ratio. Rescales before it
256
+ * divides, then truncates towards zero.
257
+ *
258
+ * @throws MathError when `b` is zero.
259
+ */
260
+ div: (a: BlockchainAmount, b: BlockchainAmount) => BlockchainAmount;
261
+ };
262
+ type AssetSymbol = Newtype<string, 'AssetSymbol'>;
263
+ declare const AssetSymbol: (value: string) => AssetSymbol;
500
264
  /**
501
- * The SDK computed something impossible. An SDK bug - please report it.
502
- * `message` is an SDK-authored template, like `validation`'s.
265
+ * A stablecoin symbol is an {@link AssetSymbol} narrowed to the coins we
266
+ * support. It shares the `AssetSymbol` brand so it stays assignable to it.
503
267
  */
504
- | {
505
- type: 'invariant';
506
- message: string;
507
- }
508
- /**
509
- * Every operand was well-formed and the arithmetic on them still had no
510
- * answer: a division by zero, a result outside the range its type can hold.
511
- * `message` is an SDK-authored template, like `validation`'s.
512
- *
513
- * Separate from `invariant` because the remedy differs. An `invariant` says
514
- * the SDK computed something impossible and is a bug to report against the
515
- * SDK; a `math` says the operation was well-posed and the operands, which
516
- * came off the wire, admit no result - so it points at the response the same
517
- * way `validation` does, one step later.
518
- */
519
- | {
520
- type: 'math';
521
- message: string;
522
- }
523
- /** An API-backed call was made without a logged-in user. */
524
- | {
525
- type: 'not-authenticated';
526
- }
527
- /** A code path the SDK has not shipped yet. */
528
- | {
529
- type: 'not-implemented';
530
- }
531
- /**
532
- * The user's wallet, or the chain, refused.
533
- *
534
- * {@link RedactedWalletError} rather than the full `WalletError`: its
535
- * `unknown` arm arrives with its `cause` dropped, that field holding the raw
536
- * wallet-library error whose message and metadata carry the transaction's
537
- * `from`, `to`, `value` and calldata. It is still on the flow's own returned
538
- * result, and on the `'debug'` line the flow seam emits beside this one.
539
- *
540
- * A type rather than a scrub applied at the call site, so an arm that grew a
541
- * raw field would fail the build.
542
- */
543
- | {
544
- type: 'wallet';
545
- error: RedactedWalletError;
546
- }
547
- /**
548
- * An on-chain read did not answer. `reason` names the remedy, not the viem
549
- * class that carried it:
550
- *
551
- * - `reverted` - the contract refused the call. Check the arguments, or the
552
- * contract's own state (a stopped swap reverts its `preview`).
553
- * - `node-rejected` - the node reached the contract and declined. Usually a
554
- * malformed request, a rate limit, or a method the node does not serve.
555
- * Also where a host-supplied `Transport` throwing something viem cannot
556
- * read a code off lands: `buildRequest` wraps it in `UnknownRpcError`.
557
- * - `transport` - the node was not reached at all. Retry, or fail over.
558
- * A dropped `webSocket()` socket included.
559
- * - `malformed-response` - the call returned something the ABI cannot
560
- * decode, which in practice means the wrong address or a stale ABI.
561
- * - `unknown` - anything else. Rare, since viem wraps most of what a read
562
- * can throw into one of the classes the arms above name.
563
- *
564
- * Decided by `instanceof` alone. Nothing is read off the thrown error - not
565
- * its `name`, not its `message`, not a revert reason - because a viem
566
- * message embeds the calldata, the contract address and the caller, and
567
- * `name` is a writable property a host-supplied transport could set to
568
- * anything. All of that is on the paired `'debug'` line.
569
- */
570
- | {
571
- type: 'rpc';
572
- reason: RpcFailureReason;
573
- }
574
- /**
575
- * Something the SDK does not recognise - usually a host-supplied lambda
576
- * (`getAccessToken`, an {@link EvmWallet} method) throwing, or a
577
- * network-level `fetch` failure.
578
- *
579
- * `name` is the error's class name and nothing else. The message and the
580
- * thrown value itself are host-authored, so they are a `'debug'` disclosure:
581
- * the paired debug line renders them in full.
582
- */
583
- | {
584
- type: 'generic-error';
268
+ type StablecoinSymbol = Newtype<'USDC' | 'USDT', 'AssetSymbol'>;
269
+ declare const StablecoinSymbol: (value: "USDC" | "USDT") => StablecoinSymbol;
270
+ type KnownAssetSymbol = StablecoinSymbol;
271
+ declare const KnownAssetSymbol: (value: "USDC" | "USDT") => StablecoinSymbol;
272
+ type Erc20Asset = {
585
273
  name: string;
274
+ symbol: AssetSymbol;
275
+ decimals: AssetDecimals;
276
+ };
277
+ type Bps = Newtype<bigint, 'Bps'>;
278
+ declare const Bps: (value: bigint) => Bps;
279
+
280
+ type AssetId = Newtype<string, 'AssetId'>;
281
+ declare const AssetId: (value: string) => AssetId;
282
+ type AssetCode = Newtype<string, 'AssetCode'>;
283
+ declare const AssetCode: (value: string) => AssetCode;
284
+ type Asset = {
285
+ id: AssetId;
286
+ code: AssetCode;
287
+ name: string;
288
+ fractionalDigits: number;
586
289
  };
587
290
 
291
+ type OfferId = Newtype<string, 'OfferId'>;
292
+ declare const OfferId: (value: string) => OfferId;
293
+ type OfferSlug = Newtype<string, 'OfferSlug'>;
294
+ declare const OfferSlug: (value: string) => OfferSlug;
295
+ type OfferType = 'coinlist::token_sale' | 'superstate::swap' | 'ondo::swap' | 'centrifuge::vault';
296
+ declare const isOfferType: (value: string) => value is OfferType;
588
297
  /**
589
- * The exhaustive chain set: a new {@link EthereumChain} is a compile error
590
- * here until it is listed, which is what keeps the constructor total.
298
+ * An offer as `GET /v1/offers` lists it: a union on {@link OfferType}, each
299
+ * variant carrying only the fields that mean something for it. Narrow on
300
+ * `type` before reading anything beyond {@link OfferBase} - an Ondo swap has
301
+ * no `startsAt` to read, and the compiler says so.
591
302
  */
592
- declare const ETHEREUM_CHAINS: Record<EthereumChain, true>;
593
- type EthereumChain = 'ethereum_mainnet' | 'ethereum_sepolia' | 'base_mainnet' | 'base_sepolia';
303
+ type Offer = CoinListTokenSaleOffer | SuperstateSwapOffer | OndoSwapOffer | CentrifugeVaultOffer;
304
+ type OfferBase = {
305
+ id: OfferId;
306
+ slug: OfferSlug;
307
+ endsAt: Date | null;
308
+ tokens: OfferToken[];
309
+ };
594
310
  /**
595
- * Validates that a raw backend string names a chain the SDK supports.
596
- *
597
- * @throws ValidationError on anything else. Passing an unknown chain through
598
- * would let downstream EIP-155 lookups blow up far from the response that
599
- * caused it.
311
+ * A curated sale window and the hand-authored copy that goes with it. Every
312
+ * variant that runs such a campaign carries all three, validated; an Ondo swap
313
+ * trades continuously and a Centrifuge vault takes deposits continuously, so
314
+ * neither runs one and neither carries any.
600
315
  */
601
- declare const EthereumChain: (value: string) => EthereumChain;
316
+ type OfferEditorial = {
317
+ tagline: string;
318
+ bannerUrl: string;
319
+ startsAt: Date;
320
+ };
321
+ type CoinListTokenSaleOffer = OfferBase & OfferEditorial & {
322
+ type: 'coinlist::token_sale';
323
+ logoUrl: string;
324
+ };
325
+ type SuperstateSwapOffer = OfferBase & OfferEditorial & {
326
+ type: 'superstate::swap';
327
+ /** Null when nobody has uploaded artwork: a swap publishes without it. */
328
+ logoUrl: string | null;
329
+ };
330
+ type OndoSwapOffer = OfferBase & {
331
+ type: 'ondo::swap';
332
+ /** Null when nobody has uploaded artwork: a swap publishes without it. */
333
+ logoUrl: string | null;
334
+ };
335
+ /** @alpha */
336
+ type CentrifugeVaultOffer = OfferBase & {
337
+ type: 'centrifuge::vault';
338
+ /** Null when nobody has uploaded artwork: a vault publishes without it. */
339
+ logoUrl: string | null;
340
+ };
602
341
  /**
603
- * The exhaustive chain set: a new {@link SolanaChain} is a compile error
604
- * here until it is listed, which is what keeps the constructor total.
342
+ * `vault` is the share token a `centrifuge::vault` offer issues for deposits,
343
+ * as `swap` is the token a swap offer trades into.
605
344
  */
606
- declare const SOLANA_CHAINS: Record<SolanaChain, true>;
607
- type SolanaChain = 'solana_mainnet' | 'solana_devnet';
345
+ type TokenRole = 'funding' | 'distribution' | 'swap' | 'vault';
346
+ type OfferToken = {
347
+ role: TokenRole;
348
+ chain: Chain;
349
+ address: EvmContractAddress;
350
+ };
351
+
352
+ type Ticker = Newtype<string, 'Ticker'>;
353
+ declare const Ticker: (value: string) => Ticker;
608
354
  /**
609
- * Validates that a raw backend string names a chain the SDK supports.
355
+ * An ISO 6166 International Securities Identification Number, e.g.
356
+ * `US0378331005`. Identifies the security itself, which is what makes it the
357
+ * stable key across venues where a {@link Ticker} is not: the same security
358
+ * trades under different tickers in different places, and the same ticker
359
+ * means different securities in different places.
610
360
  *
611
- * @throws ValidationError on anything else. Passing an unknown chain through
612
- * would let downstream EIP-155 lookups blow up far from the response that
613
- * caused it.
614
- */
615
- declare const SolanaChain: (value: string) => SolanaChain;
616
- /**
617
- * {@link Chain}'s check without the throw, for a mapper over a list that drops
618
- * the entries it cannot name rather than rejecting the whole list. Frontline's
619
- * chain registry may carry chains this SDK does not model yet.
620
- */
621
- declare const isChain: (value: string) => value is Chain;
622
- type Chain = EthereumChain | SolanaChain;
623
- declare const Chain: (value: string) => Chain;
624
- /**
625
- * Protocol a wallet binding is scoped to. EVM-only for now: an EVM address
626
- * binds once per option regardless of which EVM chain proved ownership.
627
- * Frontline's enum also has `:solana`, but we don't handle Solana bindings yet,
628
- * so this stays `'ethereum'` until Solana support lands.
361
+ * Not validated on construction — the value comes from a supplier that has
362
+ * already checked it, and a client-side check-digit test could only reject
363
+ * data the SDK still has to display.
629
364
  */
630
- type WalletProtocol = 'ethereum';
365
+ type Isin = Newtype<string, 'Isin'>;
366
+ declare const Isin: (value: string) => Isin;
367
+ type OrderBookSide = 'buy' | 'sell';
368
+
631
369
  /**
632
- * EVM addresses keep a `0x${string}` base so they stay assignable to the
633
- * `0x${string}` shapes that on-chain libraries (viem/wagmi) expect. We only
634
- * drop the runtime `0x` narrowing: values are trusted at the boundary and
635
- * branded via the constructor.
370
+ * An offer as `GET /v1/offers/:id` serves it: the same union on `type` as
371
+ * {@link Offer}, each variant carrying only the fields that mean something
372
+ * for it. Narrow on `type` before reading anything beyond
373
+ * {@link OfferDetailBase}.
636
374
  */
637
- type EvmWalletAddress = Newtype<`0x${string}`, 'EvmWalletAddress'>;
638
- declare const EvmWalletAddress: (value: string) => EvmWalletAddress;
639
- type EvmContractAddress = Newtype<`0x${string}`, 'EvmContractAddress'>;
640
- declare const EvmContractAddress: (value: string) => EvmContractAddress;
375
+ type OfferDetail = CoinListTokenSaleOfferDetail | SuperstateSwapOfferDetail | OndoSwapOfferDetail | CentrifugeVaultOfferDetail;
376
+ /** The `type` of every {@link AsyncOfferDetail}. */
377
+ type AsyncOfferType = AsyncOfferDetail['type'];
641
378
  /**
642
- * What uniquely identifies a token: the chain it is deployed on plus its
643
- * contract address. Never a symbol, which can collide across issuers.
379
+ * An offer whose trade settles later: the user places a request, the fund
380
+ * fills it on its own schedule, and the user comes back to claim the result.
381
+ * Only these have a second leg for `ClaimContainer` to show.
382
+ *
383
+ * A subset of {@link OfferDetail} picked by `type`, so it narrows like any
384
+ * other variant and a new async provider is one more member here.
385
+ *
386
+ * @alpha
644
387
  */
645
- type TokenIdentifier = {
646
- /** The chain the token contract is deployed on. */
647
- chain: EthereumChain;
388
+ type AsyncOfferDetail = CentrifugeVaultOfferDetail;
389
+ /** @alpha */
390
+ declare const AsyncOfferDetail: {
648
391
  /**
649
- * The token's contract address, in any casing — consumers that need the
650
- * EIP-55 form (e.g. the token registry routes) checksum it themselves.
392
+ * Narrows an offer fetched generically, e.g. on an offer page, to one with
393
+ * something to claim.
394
+ *
395
+ * An exhaustive `switch` rather than a list of async types, so a new offer
396
+ * type is a compile error here until someone decides whether it settles
397
+ * later.
651
398
  */
652
- address: EvmContractAddress;
399
+ is: (offer: OfferDetail) => offer is AsyncOfferDetail;
653
400
  };
654
- type HexEncodedTransactionData = Newtype<`0x${string}`, 'HexEncodedTransactionData'>;
655
- declare const HexEncodedTransactionData: (value: string) => HexEncodedTransactionData;
656
- /**
657
- * A transaction the backend has already encoded for us — the caller only
658
- * broadcasts it. Used by the flows that hand a wallet a ready-made `to`/`data`
659
- * pair (allow-listing a wallet, an Ondo swap) rather than encoding a contract
660
- * call themselves.
661
- */
662
- type Tx = {
663
- to: EvmContractAddress;
664
- data: HexEncodedTransactionData;
401
+ type CoinListTokenSaleOfferDetail = OfferDetailBase & OfferEditorial & {
402
+ type: 'coinlist::token_sale';
403
+ logoUrl: string;
404
+ };
405
+ type SuperstateSwapOfferDetail = OfferDetailBase & OfferEditorial & {
406
+ type: 'superstate::swap';
407
+ /** Null when nobody has uploaded artwork: a swap publishes without it. */
408
+ logoUrl: string | null;
409
+ };
410
+ type OndoSwapOfferDetail = OfferDetailBase & {
411
+ type: 'ondo::swap';
412
+ /** Null when nobody has uploaded artwork: a swap publishes without it. */
413
+ logoUrl: string | null;
414
+ /**
415
+ * Every chain this offer can be swapped on, with the CoinList contract each
416
+ * settles through. Empty means it cannot be swapped anywhere.
417
+ */
418
+ swapContracts: OfferSwapContract[];
419
+ };
420
+ /** @alpha */
421
+ type CentrifugeVaultOfferDetail = OfferDetailBase & {
422
+ type: 'centrifuge::vault';
423
+ /** Null when nobody has uploaded artwork: a vault publishes without it. */
424
+ logoUrl: string | null;
425
+ /**
426
+ * Every chain this vault takes deposits on, with the CoinList proxy each
427
+ * request goes through. Empty means it takes deposits nowhere yet.
428
+ */
429
+ vaultContracts: OfferVaultContract[];
430
+ };
431
+ type OfferDetailBase = {
432
+ id: OfferId;
433
+ slug: OfferSlug;
434
+ name: string;
435
+ asset: Asset;
436
+ fundingAssets: Asset[];
437
+ tokens: OfferToken[];
438
+ about: string | null;
439
+ category: string;
440
+ /**
441
+ * The legal issuer of the *token*, e.g. `'Ondo Global Markets'`. The issuer
442
+ * of the security it tracks is {@link OfferDetail.name} (`'Apple Inc.'`).
443
+ * Null on offers with no single named issuer.
444
+ */
445
+ issuer: string | null;
446
+ /**
447
+ * ISO 6166 identifier of the underlying security, e.g. `'US0378331005'`.
448
+ * Null both on offers that tokenize no security and on the few that do but
449
+ * whose supplier publishes no ISIN, so absence does not imply "not a
450
+ * security".
451
+ */
452
+ isin: Isin | null;
453
+ /**
454
+ * The underlying security's symbol on its listing venue, e.g. `'QQQ'`. This
455
+ * is *not* the token's symbol — that is `asset.code` (`'QQQon'`). Null on
456
+ * offers that tokenize no listed security.
457
+ */
458
+ ticker: Ticker | null;
459
+ /**
460
+ * What kind of instrument the underlying is, as the supplier classifies it:
461
+ * `'Stock'`, `'ETF'`. Distinct from {@link OfferDetail.category}, which is
462
+ * the broader class the offer is grouped under (`'Equities'`) — this field
463
+ * describes the single instrument, `category` describes the shelf it sits
464
+ * on.
465
+ *
466
+ * An open set: the vocabulary belongs to the supplier and may grow without
467
+ * notice, so render an unrecognized value rather than branching on it. Note
468
+ * the supplier's spelling is its own — a common stock arrives as `'Stock'`.
469
+ */
470
+ instrumentType: string | null;
471
+ /**
472
+ * The exchange the underlying security lists on, e.g. `'NASDAQ'`. Not
473
+ * derivable from {@link OfferDetail.isin}, whose country prefix is the
474
+ * country of issuance rather than the venue.
475
+ *
476
+ * Unlike the fields above, this one is recorded by hand rather than served
477
+ * by the supplier, so it is null until someone records it — on a newly
478
+ * listed offer that is the normal state, and it says nothing about whether
479
+ * the offer tokenizes a security.
480
+ */
481
+ listingVenue: string | null;
482
+ endsAt: Date | null;
483
+ faqs: FaqItem[];
484
+ links: Link[];
485
+ milestones: Milestone[];
486
+ options: OfferOption[];
487
+ terms: TermItem[];
488
+ };
489
+ type OfferOptionId = Newtype<string, 'OfferOptionId'>;
490
+ declare const OfferOptionId: (value: string) => OfferOptionId;
491
+ type OfferOptionSlug = Newtype<string, 'OfferOptionSlug'>;
492
+ declare const OfferOptionSlug: (value: string) => OfferOptionSlug;
493
+ type OfferOption = {
494
+ id: OfferOptionId;
495
+ slug: OfferOptionSlug;
496
+ bidIncrement: number | null;
497
+ floorPriceUsd: number | null;
498
+ minimumPurchaseUsd: number | null;
499
+ priceUsd: string | null;
500
+ saleAgreementUrl: string | null;
501
+ totalTokenSupply: number | null;
502
+ };
503
+ type FaqItem = {
504
+ question: string | null;
505
+ answer: string | null;
506
+ };
507
+ type TermItem = {
508
+ key: string | null;
509
+ value: string | null;
510
+ };
511
+ type Milestone = {
512
+ name: string | null;
513
+ schedule: string | null;
514
+ status: 'completed' | 'active' | 'upcoming';
515
+ };
516
+ type Link = {
517
+ label: string | null;
518
+ url: string | null;
665
519
  };
666
520
  /**
667
- * The largest exponent whose power of ten still fits a uint256: 10^77 fits,
668
- * 10^78 does not.
669
- *
670
- * The bound is arithmetic rather than a token standard's. ERC-20 reports
671
- * `decimals()` as a `uint8`, so 255 is what a contract *can* say, but every
672
- * {@link AssetDecimals} in this codebase is paired with a {@link Uint256} raw
673
- * by construction, and scaling by anything past this leaves that pairing
674
- * unrepresentable. A value between 78 and 255 would therefore pass a
675
- * standard-faithful check and then break the first multiplication it reached.
676
- */
677
- declare const MAX_ASSET_DECIMALS = 77;
678
- /**
679
- * How many decimal places one whole token divides into.
680
- *
681
- * Validated, unlike its neighbours here — {@link Uint256} is a bare `bigint`
682
- * with a separate {@link assertUint256} guard, and {@link DecimalString} casts
683
- * unchecked. The asymmetry is deliberate and follows the value's provenance:
684
- * an `AssetDecimals` used to come only from literals and the token registry,
685
- * but backends now publish exponents of their own (`asset_decimals` on an Ondo
686
- * quote, both scales on a built swap), and those feed `10n ** BigInt(d)`
687
- * during render. Guarding at construction is what keeps a fractional or
688
- * unbounded exponent from surfacing as a `RangeError` mid-render — or, at
689
- * 10^200, as a hung tab — instead of as the failed state the screen has.
690
- *
691
- * See docs/ADR-7-VALIDATED-NEWTYPES.md.
692
- *
693
- * @throws ValidationError on a non-integer, a negative, or anything above
694
- * {@link MAX_ASSET_DECIMALS}.
695
- */
696
- type AssetDecimals = Newtype<number, 'AssetDecimals'>;
697
- declare const AssetDecimals: (value: number) => AssetDecimals;
698
- declare const STABLE_DECIMALS: AssetDecimals;
699
- /**
700
- * A non-negative decimal number the backend sent as a string, kept unscaled.
701
- *
702
- * Reach for it when a response gives you a number but not the decimals to
703
- * scale it by, or gives you one it has already applied. Inventing an exponent
704
- * for either is how a display ends up orders of magnitude out.
521
+ * A chain an offer can be swapped on, with the CoinList contract the swap
522
+ * settles through - the spender to approve before any quote exists.
705
523
  *
706
- * Contrast {@link BlockchainAmount}, which pairs a raw uint256 with the
707
- * decimals it is denominated in and so is only constructible when you know
708
- * both. Arithmetic on a `DecimalString` needs a decimal library we
709
- * deliberately do not ship.
524
+ * Carries no role, unlike {@link OfferToken}: there is one swap contract per
525
+ * chain and it serves every Ondo asset on it, so which token is being traded
526
+ * does not change the answer.
710
527
  */
711
- type DecimalString = Newtype<string, 'DecimalString'>;
712
- declare const DecimalString: (value: string) => DecimalString;
713
- declare const MAX_UINT_256: bigint;
528
+ type OfferSwapContract = {
529
+ chain: Chain;
530
+ address: EvmContractAddress;
531
+ };
714
532
  /**
715
- * A non-negative integer within uint256 bounds. Kept unbranded (a plain
716
- * `bigint`) so raw on-chain amounts flow in without ceremony; bounds are
717
- * enforced where it matters (see {@link combineAmounts}).
533
+ * A chain a Centrifuge vault takes deposits on, with the CoinList proxy the
534
+ * request goes through. Not a token, so the token registry knows nothing about
535
+ * it: it is the spender the user approves the funding token to, and the `to`
536
+ * the buy and claim transactions are sent to.
537
+ *
538
+ * Its own type rather than an {@link OfferSwapContract}, although the shape is
539
+ * the same, because the meaning is not: a swap contract serves every Ondo
540
+ * asset on its chain, while a proxy belongs to one offer on one chain, so the
541
+ * same chain carries a different address on every vault.
542
+ *
543
+ * @alpha
718
544
  */
719
- type Uint256 = bigint;
545
+ type OfferVaultContract = {
546
+ chain: Chain;
547
+ address: EvmContractAddress;
548
+ };
549
+
720
550
  /**
721
- * Asserts a computed bigint falls within uint256 bounds. Use at on-chain
722
- * arithmetic boundaries (bps math, price computation) where the SDK's own
723
- * arithmetic could underflow below zero or overflow above 2^256-1.
551
+ * One vault: a share token on one chain. What the status and position reads
552
+ * are keyed by, since an offer runs one vault per chain it has a proxy on.
724
553
  *
725
- * Throws {@link InvariantError}, because reaching it means the SDK computed
726
- * something no chain could represent. **For a value that came off the wire,
727
- * use {@link parseUint256} instead**: the backend sending an unrepresentable
728
- * number is a {@link ValidationError}, and the two have different remedies.
554
+ * @alpha
729
555
  */
730
- declare const assertUint256: (value: bigint) => Uint256;
556
+ type CentrifugeVaultRef = {
557
+ /** Share token symbol of the vault, e.g. `deJTRSY`. */
558
+ symbol: AssetSymbol;
559
+ chain: EthereumChain;
560
+ };
731
561
  /**
732
- * {@link assertUint256} at a wire boundary: the same bounds check, reported as
733
- * a {@link ValidationError} against the named field, because a value out of
734
- * range here is the backend's, not the SDK's.
562
+ * Which side of a vault trade: `deposit` (buy) turns the funding token into
563
+ * shares, `redeem` (sell) turns shares back into the funding token. The
564
+ * contract's own names. Each wallet holds one pooled position per vault per
565
+ * direction, and a claim settles one of them.
735
566
  *
736
- * `label` names the field the way a DTO mapper's other failures do, so a log
737
- * line says which one - `SwapPreview.pay_input_amount`, not `a number`.
567
+ * @alpha
738
568
  */
739
- declare const parseUint256: (value: bigint, label: string) => Uint256;
740
- type BlockchainAmount = Newtype<{
741
- raw: Uint256;
742
- decimals: AssetDecimals;
743
- }, 'BlockchainAmount'>;
569
+ type CentrifugeClaimDirection = 'deposit' | 'redeem';
744
570
  /**
745
- * Constructs a {@link BlockchainAmount} and exposes arithmetic helpers.
746
- * TypeScript has no operator overloading, so use `BlockchainAmount.add(a, b)`
747
- * instead of `+`/`-`/`*`/`/` on the objects directly.
571
+ * Why a deposit cannot be placed on a vault right now. Kept verbatim from the
572
+ * wire: frontline publishes it as a closed set for clients to branch on.
573
+ *
574
+ * @alpha
748
575
  */
749
- declare const BlockchainAmount: ((value: {
750
- raw: Uint256;
751
- decimals: AssetDecimals;
752
- }) => BlockchainAmount) & {
753
- add: (a: BlockchainAmount, b: BlockchainAmount) => BlockchainAmount;
754
- sub: (a: BlockchainAmount, b: BlockchainAmount) => BlockchainAmount;
755
- mul: typeof multiplyAmounts;
756
- div: typeof divideAmounts;
757
- };
576
+ type CentrifugeOrderReason = 'vault_unlinked' | 'vault_halted' | 'proxy_not_permissioned' | 'price_unavailable' | 'below_minimum';
758
577
  /**
759
- * `a * b`, denominated in `a`'s decimals - a price times a quantity, an amount
760
- * times a rate.
578
+ * The reasons a vault status can report - all but `below_minimum`.
761
579
  *
762
- * The exact inverse of {@link divideAmounts}, and it reads the same way round:
763
- * the answer stays in `a`'s scale and `b`'s divides back out, so the two
764
- * compose - `mul(div(a, b), b)` is `a` again, short only what truncation took.
765
- * That is what makes `price x quantity` land in the currency the price was
766
- * quoted in rather than at some product of two exponents no token uses.
767
- *
768
- * The multiplication happens *before* the division by `10^b.decimals`, so the
769
- * full precision of both operands survives into the one rounding at the end.
770
- * Like `div` it truncates towards zero on both signs, which is the safe
771
- * direction for money: a total is never inflated past what the parts hold.
772
- *
773
- * Total - unlike `div`, there is nothing here to reject. The scale it divides
774
- * by is a power of ten, never zero, and a product is not bounds-checked for
775
- * the same reason a quotient is not: whether one past uint256 is a corrupt
776
- * response or an expected magnitude belongs to the caller.
580
+ * @alpha
777
581
  */
778
- declare function multiplyAmounts(a: BlockchainAmount, b: BlockchainAmount): BlockchainAmount;
582
+ type CentrifugeVaultReason = Exclude<CentrifugeOrderReason, 'below_minimum'>;
779
583
  /**
780
- * `a / b`, denominated in `a`'s decimals - a price, a ratio, a rate.
781
- *
782
- * Unlike {@link combineAmounts}, the two operands may be at different scales,
783
- * and usually are: dividing dollars by shares is the point of the operation,
784
- * and the two tokens rarely share an exponent. `b`'s scale divides back out -
785
- * the numerator is scaled by `10^b.decimals` *before* the division, so nothing
786
- * is lost to integer truncation early and the quotient lands in `a`'s scale,
787
- * which is the one the amounts it is rendered beside are in.
788
- *
789
- * Truncates towards zero on both signs, which is what BigInt division already
790
- * does and the safe direction for money: the total a price implies never
791
- * exceeds, in magnitude, the amount that actually moved.
584
+ * Whether a deposit can be placed, as one union rather than the wire's
585
+ * `can_order` beside `reasons`: a blocked answer always says why, and an
586
+ * orderable one has nothing to say. The wire pair can contradict itself; this
587
+ * cannot, because `fromDto` refuses the payloads that do.
792
588
  *
793
- * A zero divisor is the only rejection. The quotient is deliberately *not*
794
- * bounds-checked the way {@link combineAmounts} checks a sum: scaling the
795
- * numerator carries even legal pairs past uint256 (a maximal amount over a
796
- * single base unit), and whether that reads as a corrupt response or an
797
- * expected magnitude is the caller's to judge - see `computeOndoBuyPrice` and
798
- * `computeOndoSellPrice`, which bounds-check the price they build out of this.
799
- *
800
- * @throws MathError when `b` is zero. `add` and `sub` throw a bare `Error`
801
- * because a scale mismatch is a programming error caught in review; a zero
802
- * divisor arrives from a server response and is divided by during render, so
803
- * it is a named failure a screen can catch and map to a failed state.
589
+ * @alpha
804
590
  */
805
- declare function divideAmounts(a: BlockchainAmount, b: BlockchainAmount): BlockchainAmount;
806
- type AssetSymbol = Newtype<string, 'AssetSymbol'>;
807
- declare const AssetSymbol: (value: string) => AssetSymbol;
591
+ type CentrifugeOrderability<R extends CentrifugeOrderReason> = {
592
+ type: 'orderable';
593
+ } | {
594
+ type: 'blocked';
595
+ reasons: [R, ...R[]];
596
+ };
808
597
  /**
809
- * A stablecoin symbol is an {@link AssetSymbol} narrowed to the coins we
810
- * support. It shares the `AssetSymbol` brand so it stays assignable to it.
598
+ * A vault's own state, independent of any wallet or amount: the gate to poll.
599
+ * Centrifuge announces none of these changes, and an unlinked vault leaves the
600
+ * offer's proxy permanently dead, so a cached answer goes wrong silently.
601
+ *
602
+ * @alpha
811
603
  */
812
- type StablecoinSymbol = Newtype<'USDC' | 'USDT', 'AssetSymbol'>;
813
- declare const StablecoinSymbol: (value: "USDC" | "USDT") => StablecoinSymbol;
814
- type KnownAssetSymbol = StablecoinSymbol;
815
- declare const KnownAssetSymbol: (value: "USDC" | "USDT") => StablecoinSymbol;
816
- type Erc20Asset = {
817
- name: string;
818
- symbol: AssetSymbol;
819
- decimals: AssetDecimals;
820
- };
821
- type Bps = Newtype<bigint, 'Bps'>;
822
- declare const Bps: (value: bigint) => Bps;
823
-
824
- type GetTokenAllowanceParams = {
825
- tokenAddress: EvmContractAddress;
826
- owner: EvmWalletAddress;
827
- spender: EvmContractAddress;
828
- chain: EthereumChain;
829
- };
830
- type GetTokenBalanceParams = {
831
- tokenAddress: EvmContractAddress;
832
- owner: EvmWalletAddress;
604
+ type CentrifugeVaultStatus = {
833
605
  chain: EthereumChain;
606
+ symbol: AssetSymbol;
607
+ /** Centrifuge's vault behind the proxy. Fixed for the proxy's life. */
608
+ vaultAddress: EvmContractAddress;
609
+ /**
610
+ * Whether Centrifuge still links the vault to its pool. False is terminal
611
+ * for this proxy: no request or claim succeeds until a new one is deployed.
612
+ */
613
+ linked: boolean;
614
+ /**
615
+ * Whether the proxy is on the share token's memberlist. It holds shares for
616
+ * every wallet, so one expired entry stops the whole offer.
617
+ */
618
+ permissioned: boolean;
619
+ /** Watch it: {@link permissioned} goes false the moment it passes. */
620
+ membershipValidUntil: Date | null;
621
+ /** The proxy halted itself after a force-cancel; cleared only by hand. */
622
+ halted: boolean;
623
+ /** When the vault last published a price. A stale value means the price is old. */
624
+ priceUpdatedAt: Date | null;
625
+ orderability: CentrifugeOrderability<CentrifugeVaultReason>;
626
+ /** When the chain was read: how old everything above is. */
627
+ asOf: Date;
834
628
  };
835
-
836
629
  /**
837
- * OAuth 2.0 token response (RFC 6749 §5.1).
838
- * Maps to OpenAPI schema OauthToken.
630
+ * What both vault transactions share: calldata to broadcast verbatim, and the
631
+ * deadline on it.
632
+ *
633
+ * @alpha
839
634
  */
840
- type OAuthSessionDto = {
841
- access_token: string;
842
- expires_in: number;
843
- refresh_token?: string;
844
- };
845
-
846
- type ClientCredentialsOAuth = Newtype<OAuthAccessToken, 'ClientCredentialsOAuth'>;
847
- declare const ClientCredentialsOAuth: (value: OAuthAccessToken) => ClientCredentialsOAuth;
848
- type OAuthAccessToken = {
849
- value: string;
850
- expiresAt: Date;
851
- };
852
- type OAuthRefreshToken = Newtype<string, 'OAuthRefreshToken'>;
853
- declare const OAuthRefreshToken: (value: string) => OAuthRefreshToken;
854
- type OAuthSession = {
855
- accessToken: OAuthAccessToken;
856
- refreshToken?: OAuthRefreshToken;
857
- };
858
- declare const OAuthSession: {
859
- fromDto: (dto: OAuthSessionDto) => OAuthSession;
635
+ type CentrifugeVaultTransactionCore = {
636
+ /** The `to` is the offer's proxy: the spender the deposit was approved to. */
637
+ tx: Tx;
638
+ /**
639
+ * When the calldata stops being accepted, or null when it carries no
640
+ * deadline - always the case for a claim, which nobody signs.
641
+ */
642
+ expiresAt: Date | null;
860
643
  };
861
-
862
- type HttpRequestAttributes = {
863
- /**
864
- * Correlates the log lines of one logical request. Minted by
865
- * {@link HttpClient} on the first attempt and carried onto every retry and
866
- * post-renewal re-send by {@link concat}, which is what lets a reader tell
867
- * three attempts of one request from three separate requests.
868
- */
869
- requestId?: RequestId;
870
- protected?: boolean;
871
- userAgent?: boolean;
872
- idempotencyKey?: boolean;
873
- /** Zero-based attempt index: 0 = first request, 1 = first retry, etc. */
874
- retryAttempt?: number;
875
- renewAttempted?: boolean;
876
- clientCredentials?: ClientCredentialsOAuth;
644
+ /**
645
+ * A signed deposit request, ready to broadcast.
646
+ *
647
+ * **Broadcasting it buys nothing yet.** It places a request in the vault's
648
+ * queue; the fund fulfils it later at a price set then, and the user comes
649
+ * back to claim. Single-use: built once on confirmation, never on a timer.
650
+ *
651
+ * @alpha
652
+ */
653
+ type CentrifugeVaultBuyTransaction = CentrifugeVaultTransactionCore & {
654
+ /** Exactly what the approval has to cover: no fee comes off it. */
655
+ spendInputAmount: BlockchainAmount;
877
656
  };
878
-
879
657
  /**
880
- * The value types a query-string parameter may take.
658
+ * The claim that moves fulfilled shares from the proxy to the wallet: the
659
+ * second transaction every vault purchase needs. The wallet that broadcasts it
660
+ * is the one credited.
881
661
  *
882
- * Declared here rather than beside the HTTP client because domain models build
883
- * query parameters too: `ParticipationsPaginationParams.toQueryParams` returns
884
- * a `Record<string, QueryParamValue>`, and a domain model must not import
885
- * `@/universal/api/**`.
662
+ * @alpha
886
663
  */
887
- type QueryParamValue = string | number | boolean | null | undefined;
888
- type QueryParamValues = QueryParamValue | QueryParamValue[];
889
-
890
- type HttpRequest<TBody = unknown> = {
891
- method: 'GET';
892
- url: string;
893
- queryParams?: Record<string, QueryParamValues>;
894
- headers?: Record<string, string>;
895
- attributes?: HttpRequestAttributes;
896
- redirect?: RequestRedirect;
897
- } | {
898
- method: 'POST';
899
- url: string;
900
- queryParams?: Record<string, QueryParamValues>;
901
- headers?: Record<string, string>;
902
- body: TBody;
903
- attributes?: HttpRequestAttributes;
904
- redirect?: RequestRedirect;
905
- } | {
906
- method: 'DELETE';
907
- url: string;
908
- queryParams?: Record<string, QueryParamValues>;
909
- headers?: Record<string, string>;
910
- attributes?: HttpRequestAttributes;
911
- redirect?: RequestRedirect;
912
- };
913
- type HttpResponse<TBody = unknown> = {
914
- status: number;
915
- headers?: Record<string, string>;
916
- body: TBody | null;
664
+ type CentrifugeVaultClaimTransaction = CentrifugeVaultTransactionCore & {
917
665
  /**
918
- * The id of the request that produced this response, stamped by
919
- * {@link HttpClient} so that whoever turns a non-2xx into an
920
- * {@link HttpError} can carry it without threading the request alongside.
921
- * Absent when the response did not come from an `HttpClient`.
666
+ * Read when the calldata was built. A floor: a fill landing before the
667
+ * broadcast raises it, and the claim still collects everything.
922
668
  */
923
- requestId?: RequestId;
669
+ claimableShares: BlockchainAmount;
924
670
  };
925
- declare class HttpError<TBody = unknown> extends Error {
926
- readonly response: HttpResponse<TBody>;
927
- constructor(response: HttpResponse<TBody>);
928
- /**
929
- * Correlates this failure with the `[HTTP]` log lines for the same request,
930
- * which carry the method, the URL, the duration and every retry. `null` when
931
- * the response did not come from an {@link HttpClient}.
932
- *
933
- * Worth quoting in a bug report: it is what makes a log excerpt readable.
934
- */
935
- get requestId(): RequestId | null;
936
- }
937
671
  /**
938
- * The machine-readable `code` frontline attaches to an error, or `null` for
939
- * anything else - a non-HTTP failure, or an error body that carries only a
940
- * message.
672
+ * The funding token a vault is paid in, in that token's decimals. Nominally
673
+ * distinct from {@link CentrifugeShares}: the two legs settle in opposite
674
+ * units, and the compiler, not a comment, keeps one from standing in for the
675
+ * other.
941
676
  *
942
- * Frontline answers a failure with `{ type, message, code?, errors?,
943
- * event_id? }`, and `code` is the only part of it meant to be branched on:
944
- * `message` is prose that may be reworded, and `type` distinguishes
945
- * `invalid_request_error` from `api_error` without saying which one.
677
+ * @alpha
678
+ */
679
+ type CentrifugeAssets = BlockchainAmount & {
680
+ readonly [centrifugeUnit]: 'assets';
681
+ };
682
+ /** @alpha */
683
+ declare const CentrifugeAssets: (amount: BlockchainAmount) => CentrifugeAssets;
684
+ /**
685
+ * A vault's share token, in its decimals. See {@link CentrifugeAssets}.
946
686
  *
947
- * Reach for this only where a code has a distinct remedy the user can act on.
948
- * Mapping the whole vocabulary would couple the SDK to strings frontline does
949
- * not version; falling back to a generic failure is the right default.
687
+ * @alpha
950
688
  */
951
- declare function apiErrorCode(error: unknown): string | null;
952
-
689
+ type CentrifugeShares = BlockchainAmount & {
690
+ readonly [centrifugeUnit]: 'shares';
691
+ };
692
+ /** @alpha */
693
+ declare const CentrifugeShares: (amount: BlockchainAmount) => CentrifugeShares;
953
694
  /**
954
- * Structural interface satisfied by both {@link AuthenticatedApiClient} and
955
- * {@link ApiClient}. Used by the shared frontline API functions so they can
956
- * be called from either the client or server without any browser dependencies.
695
+ * The phantom key {@link CentrifugeAssets} and {@link CentrifugeShares} are
696
+ * told apart by. A second key rather than a second `Newtype`, because
697
+ * `BlockchainAmount` already spends the `Newtype` brand: two brands on one key
698
+ * intersect to `never`, and the units would become assignable to each other.
957
699
  */
958
- interface Sender {
959
- send<T>(request: HttpRequest): Promise<T>;
960
- }
961
-
700
+ declare const centrifugeUnit: unique symbol;
962
701
  /**
963
- * Where the SDK sends its on-chain reads, per chain.
702
+ * One wallet's Deposit (buy) standing on one vault, read from the proxy.
964
703
  *
965
- * Leave it undefined and reads go through CoinList's authenticated JSON-RPC
966
- * proxy (`POST /v1/chains/{chain}/rpc`), which needs no setup but does need a
967
- * logged-in user - the proxy is OAuth-gated, so a read on a chain the SDK is
968
- * proxying fails with its `401`, surfaced as the viem error wrapping it. A
969
- * chain you name here is read straight from your own node instead, and needs
970
- * no CoinList session at all.
971
- *
972
- * Each entry is either:
973
- *
974
- * - an RPC URL, which the SDK wraps in viem's `http()` transport, or
975
- * - a viem {@link Transport} used as it stands - `fallback([...])`,
976
- * `webSocket()`, or your app's own.
704
+ * The proxy keeps one pooled position per wallet: a FIFO slice of the vault's
705
+ * queue, which the fund fills on its own schedule. `pending` and `claimable`
706
+ * coexist - a fill can cover part of the slice - and neither says anything
707
+ * about earlier claims.
977
708
  *
978
- * Chains you leave out fall back to the proxy individually, so overriding
979
- * mainnet does not opt Sepolia out. The SDK always constructs the viem
980
- * `PublicClient` itself, so the chain definition, batching and retry policy
981
- * stay the SDK's regardless of the transport you supply.
709
+ * Both are in the funding token, the unit the proxy tracks a deposit in. The
710
+ * shares a claim pays are not here: the proxy publishes no claim price, and
711
+ * its spot `convertToShares` is documented as not being one.
982
712
  *
983
- * @example
984
- * ```ts
985
- * createCoinListClient({
986
- * clientId,
987
- * redirectUri,
988
- * rpc: {
989
- * ethereum_mainnet: 'https://eth-mainnet.g.alchemy.com/v2/KEY',
990
- * ethereum_sepolia: fallback([http(primary), http(backup)]),
991
- * // base_* unset -> proxied through CoinList
992
- * },
993
- * });
994
- * ```
713
+ * @alpha
995
714
  */
996
- type RpcConfig = Partial<Record<EthereumChain, string | Transport>>;
997
- interface Config {
998
- /** OAuth2 public identifier. */
999
- readonly clientId: ClientId;
1000
- /**
1001
- * OAuth2 redirect URI. Recommended to point to a frontend page where
1002
- * {@link CoinListClient#completeOauth} can be called to complete the PKCE
1003
- * flow on the client side.
1004
- */
1005
- readonly redirectUri: RedirectUri;
1006
- /**
1007
- * Recommended to leave undefined. Used to change the CoinList environment;
1008
- * default is production.
1009
- */
1010
- readonly baseUrl?: string;
1011
- /**
1012
- * Recommended to leave undefined. Overrides the base URL of the public
1013
- * token registry backing `coinlist.tokens`; default is the production
1014
- * registry.
1015
- */
1016
- readonly tokensBaseUrl?: string;
1017
- /**
1018
- * Where the SDK reports what it is doing. **Omit it and the SDK logs nothing
1019
- * at all** - no `console` fallback, at any level, on any codepath. Supply
1020
- * one and every request, every classified failure and every hook state
1021
- * transition is reported at the level your logger asks for.
1022
- *
1023
- * Absent, the SDK says nothing at all - there is no fallback to `console`,
1024
- * at any level, on any codepath.
1025
- *
1026
- * **Running the SDK's logging in production is not advised.** The safest
1027
- * posture is to leave this undefined outside development, staging and
1028
- * incident reproduction: a seam that emits nothing cannot disclose anything,
1029
- * and that property does not depend on the SDK continuing to get redaction
1030
- * right.
1031
- *
1032
- * If you do run one there, run it at `'info'` or above and know what that
1033
- * does and does not buy you. Those levels are **redacted by construction**:
1034
- * they carry only SDK-authored classification and server-authored
1035
- * identifiers, and the type system holds that line rather than a convention -
1036
- * an event at those levels accepts scalar fields only, so a body, a DTO or
1037
- * an operation's parameters cannot be put on one. What the SDK does **not**
1038
- * give you is a warranty that the result is safe for your environment. The
1039
- * mechanism is checkable and stated; the conclusion depends on your sink,
1040
- * your retention and your threat model, and it is yours to draw.
1041
- *
1042
- * **`'debug'` is not.** It reports request and response bodies, full URLs,
1043
- * headers and operation parameters verbatim - bearer tokens, KYC answers,
1044
- * tax-document fields, wallet signatures - and **the SDK does not redact**.
1045
- * Run it on a developer's machine, in tests, and in beta or staging
1046
- * environments where the data flowing through is not real customer data.
1047
- * The shipped implementations make that structural: built with
1048
- * `isDev: false`, `'debug'` does not typecheck. Either way, filtering,
1049
- * redaction and retention at your sink are yours, not the SDK's.
1050
- *
1051
- * Every method you implement here **must be total**: the SDK calls them on
1052
- * the codepath of the work they report and does not catch them, so a logger
1053
- * that throws fails the operation it was describing.
1054
- *
1055
- * See {@link Logger} for the full contract, and {@link pinoClientLogger} or
1056
- * {@link pinoServerLogger} for a ready-made implementation over pino.
1057
- */
1058
- readonly logger?: Logger;
1059
- /**
1060
- * Where on-chain reads go. Omit it and they are proxied through CoinList.
1061
- * See {@link RpcConfig}.
1062
- */
1063
- readonly rpc?: RpcConfig;
1064
- }
1065
-
715
+ type CentrifugeDepositPosition = {
716
+ chain: EthereumChain;
717
+ /** The CoinList proxy the position lives on. */
718
+ proxy: EvmContractAddress;
719
+ walletAddress: EvmWalletAddress;
720
+ /** Still queued, not yet filled: `pendingDepositRequest`. */
721
+ pending: CentrifugeAssets;
722
+ /** Filled and not yet claimed: `claimableDepositRequest`. A claim takes it all. */
723
+ claimable: CentrifugeAssets;
724
+ };
1066
725
  /**
1067
- * Resolves a chain to the viem `PublicClient` the SDK reads it through.
726
+ * One wallet's Redeem (sell) standing on one vault, read from the proxy. The
727
+ * mirror of {@link CentrifugeDepositPosition}, in shares, and as independent
728
+ * of it: a wallet can deposit and redeem on one vault at once.
1068
729
  *
1069
- * One function rather than a client per read, because the client is where the
1070
- * batching lives: viem's multicall scheduler is keyed on the client's `uid`,
1071
- * so two reads only ever share an `eth_call` if they share a client. Hence the
1072
- * memoisation - it is correctness for batching, not a performance tweak.
730
+ * @alpha
1073
731
  */
1074
- type PublicClients = (chain: EthereumChain) => PublicClient;
1075
-
1076
- interface UniversalNamespaceContext {
1077
- readonly api: Sender;
1078
- /**
1079
- * The viem `PublicClient` for a chain, over whichever transport that chain
1080
- * resolved to - the host's own node, or CoinList's proxy.
1081
- *
1082
- * A factory rather than a client, because a namespace serves every chain and
1083
- * only the call knows which. Built once per client instance so that
1084
- * concurrent reads share a batch; see `publicClients`.
1085
- */
1086
- readonly rpc: PublicClients;
1087
- /**
1088
- * The host's logger, or `null` when they supplied none.
1089
- *
1090
- * Deliberately the raw port rather than a pre-scoped {@link InternalLogger}:
1091
- * one context serves every namespace, and each namespace owns its own scope.
1092
- * A namespace turns it into something usable in its constructor, with
1093
- * `internalLogger(ctx.logger, 'OFFERS')`.
1094
- */
1095
- readonly logger: Logger | null;
1096
- ensureUserAuthenticated(): Promise<void>;
1097
- }
1098
-
732
+ type CentrifugeRedeemPosition = {
733
+ chain: EthereumChain;
734
+ /** The CoinList proxy the position lives on. */
735
+ proxy: EvmContractAddress;
736
+ walletAddress: EvmWalletAddress;
737
+ /** Still queued, not yet filled: `pendingRedeemRequest`. */
738
+ pending: CentrifugeShares;
739
+ /** Filled and not yet claimed: `claimableRedeemRequest`. A claim takes it all. */
740
+ claimable: CentrifugeShares;
741
+ };
1099
742
  /**
1100
- * Generic ERC-20 reads shared across on-chain flows (swap, token sale): the
1101
- * allowance an owner has granted a spender, and the raw token balance an owner
1102
- * holds. These are plain token reads, not tied to any single product flow.
743
+ * One wallet's position on one side of a vault trade, tagged with the side.
744
+ * The two sides are tracked in opposite units - the funding token for
745
+ * `deposit`, shares for `redeem` - so the tag is what says which unit each
746
+ * figure is in.
1103
747
  *
1104
- * Read from the chain rather than from a CoinList endpoint. By default that
1105
- * goes through CoinList's authenticated JSON-RPC proxy, so a logged-out caller
1106
- * gets the proxy's `401` - surfaced as a viem error wrapping the SDK's
1107
- * `HttpError`, not as `NotAuthenticatedError`, since viem re-wraps whatever a
1108
- * `Transport` throws. Point `Config.rpc` at your own node for this chain and no
1109
- * CoinList session is involved at all. See {@link RpcConfig}.
1110
- */
1111
- interface Erc20Namespace {
1112
- /**
1113
- * Reads the ERC-20 allowance an `owner` has granted a `spender`, as a raw
1114
- * on-chain integer.
1115
- */
1116
- getAllowance(params: GetTokenAllowanceParams): Promise<Uint256>;
1117
- /**
1118
- * Reads the raw ERC-20 balance an `owner` holds of a token.
1119
- */
1120
- getBalance(params: GetTokenBalanceParams): Promise<Uint256>;
1121
- }
1122
- declare class Erc20NamespaceImpl implements Erc20Namespace {
1123
- private readonly ctx;
1124
- private readonly log;
1125
- constructor(ctx: UniversalNamespaceContext);
1126
- getAllowance(params: GetTokenAllowanceParams): Promise<Uint256>;
1127
- getBalance(params: GetTokenBalanceParams): Promise<Uint256>;
1128
- }
1129
-
1130
- /**
1131
- * The logging surface SDK code uses. Never exported to hosts: they implement
1132
- * {@link Logger}, which is deliberately smaller.
748
+ * @alpha
1133
749
  */
1134
- type InternalLogger = {
1135
- /**
1136
- * A logger narrowed by one more {@link LogBinding}, which every line it
1137
- * emits carries as its own field.
1138
- */
1139
- child(binding: LogBinding): InternalLogger;
1140
- /**
1141
- * The unredacted level. Request and response bodies, full URLs, headers,
1142
- * operation params and raw thrown errors belong here and **only** here.
1143
- */
1144
- debug(event: () => InternalDebugEvent): void;
1145
- info(event: () => InternalSafeEvent): void;
1146
- warn(event: () => InternalSafeEvent): void;
1147
- /**
1148
- * Reports a failure whose cause is already known, or has none. Prefer
1149
- * {@link failure}, which classifies a thrown error for you.
1150
- */
1151
- error(event: () => InternalSafeEvent): void;
1152
- /**
1153
- * Reports a thrown error, classifying it into a {@link LogCause}. Prefer
1154
- * this over `error` at a `catch`: classification only happens when the level
1155
- * admits it.
1156
- *
1157
- * The `error` line names the error's class but never its message, since an
1158
- * error the SDK did not author may say anything at all. The verbatim
1159
- * rendering goes onto a paired `debug` line instead.
1160
- *
1161
- * For a failure the SDK recovers from, use {@link warning} instead. An
1162
- * `error` line a host cannot act on is worse than no line at all: it trains
1163
- * them to ignore the ones that matter.
1164
- */
1165
- failure(event: () => InternalSafeEvent, error: unknown): void;
1166
- /**
1167
- * {@link failure} for trouble that did not break anything: a poll tick that
1168
- * failed while the last value still stands, a retried request.
1169
- *
1170
- * Carries the classified {@link LogCause} exactly as `failure` does. The two
1171
- * differ in level and in nothing else, because what separates them is
1172
- * whether anything actually broke - not how much is known about it.
1173
- */
1174
- warning(event: () => InternalSafeEvent, error: unknown): void;
1175
- /**
1176
- * Runs one namespace operation with logging around it: the call and its
1177
- * params at `debug`, and any throw at `error` with the cause classified.
1178
- * Every line carries `op` as a binding.
1179
- *
1180
- * **The params never reach the `error` line.** They routinely hold the
1181
- * things this SDK must not put in front of a production error tracker - an
1182
- * app bearer token, a document's signing fields, a wallet signature - and
1183
- * the `debug` line plus the request id is how you get them back. The type
1184
- * says so: `params` is `unknown`, which is not a {@link SafeFields} value.
1185
- *
1186
- * Always rethrows. Logging observes behaviour, it never changes it.
1187
- */
1188
- wrap<T>(op: string, params: unknown, run: () => Promise<T>): Promise<T>;
750
+ type CentrifugeVaultLeg = {
751
+ direction: 'deposit';
752
+ position: CentrifugeDepositPosition;
753
+ } | {
754
+ direction: 'redeem';
755
+ position: CentrifugeRedeemPosition;
756
+ };
757
+ /** @alpha */
758
+ declare const CentrifugeVaultLeg: {
759
+ /** Whether the leg has anything queued or anything ready to claim. */
760
+ isActive: (leg: CentrifugeVaultLeg) => boolean;
1189
761
  };
1190
762
  /**
1191
- * What a call site hands to `info`, `warn`, `error`, `failure` or `warning` -
1192
- * a {@link SafeEvent} minus the scope and bindings the logger stamps itself.
763
+ * One wallet's position on one leg of one vault: what a claim targets. A
764
+ * wallet holds at most one per direction per chain, so the three together
765
+ * name it.
766
+ *
767
+ * @alpha
1193
768
  */
1194
- type InternalSafeEvent = {
1195
- /** A **constant** string literal. Every varying value belongs in `fields`. */
1196
- readonly msg: string;
1197
- readonly fields?: SafeFields;
1198
- readonly cause?: LogCause;
1199
- };
1200
- /** What a call site hands to `debug`. Its fields accept anything. */
1201
- type InternalDebugEvent = {
1202
- /** A **constant** string literal. Every varying value belongs in `fields`. */
1203
- readonly msg: string;
1204
- readonly fields?: UnredactedFields;
769
+ type CentrifugeVaultLegId = Newtype<string, 'CentrifugeVaultLegId'>;
770
+ /** @alpha */
771
+ declare const CentrifugeVaultLegId: {
772
+ /** Addresses compare case-insensitively: a host may pass either case. */
773
+ of: (leg: CentrifugeVaultLeg) => CentrifugeVaultLegId;
1205
774
  };
1206
-
1207
775
  /**
1208
- * The product an offer is checked out through. The backend sends one compound
1209
- * `{supplier}::{saleType}` string, verbatim as spelled below: a double colon
1210
- * between the two halves, `snake_case` within each. CoinList's token sale is
1211
- * `coinlist::token_sale`; there is no `coinlist::sale` and no
1212
- * `coinlist_token_sale`.
776
+ * The claim that pays a filled redemption out in the funding token: the
777
+ * second transaction every vault sale needs. The wallet that broadcasts it is
778
+ * the one paid.
1213
779
  *
1214
- * Kept flat rather than split into a supplier and a sale type, because a flat
1215
- * union makes a `switch` over it exhaustive: adding a provider without
1216
- * handling it becomes a compile error.
780
+ * TODO(frontline, ENG-1909): built against a guessed wire shape - see
781
+ * `CentrifugeVaultClaimProceedsDto`.
782
+ *
783
+ * @alpha
1217
784
  */
1218
- type OfferTypeDto = 'coinlist::token_sale' | 'superstate::swap' | 'ondo::swap';
785
+ type CentrifugeVaultClaimProceedsTransaction = CentrifugeVaultTransactionCore & {
786
+ /** An estimate read when the calldata was built; the claim pays it all. */
787
+ claimableAssets: BlockchainAmount;
788
+ };
789
+
1219
790
  /**
1220
- * One variant per {@link OfferTypeDto}, mirroring frontline#1365, which keys
1221
- * `GET /v1/offers` on `type`. Read `type` first: it decides which of the
1222
- * fields below mean anything.
791
+ * An absolute URL to a logo image in the token registry. Registry image URLs
792
+ * are content-fingerprinted and served immutable, so a value is safe to cache
793
+ * for as long as you hold it.
1223
794
  */
1224
- type OfferDto = CoinListTokenSaleOfferDto | SuperstateSwapOfferDto | OndoSwapOfferDto;
1225
- type OfferBaseDto = {
1226
- id: string;
1227
- slug: string;
1228
- ends_at: string | null;
1229
- tokens: OfferTokenDto[];
795
+ type TokenLogoUrl = Newtype<string, 'TokenLogoUrl'>;
796
+ declare const TokenLogoUrl: (value: string) => TokenLogoUrl;
797
+ type TokenLogoImage = {
798
+ url: TokenLogoUrl;
799
+ width: number;
800
+ height: number;
1230
801
  };
1231
802
  /**
1232
- * A curated sale window and the hand-authored copy that goes with it. Required
1233
- * on every variant that runs such a campaign, which is every one but Ondo.
803
+ * `VECTOR` is a single SVG. `RASTER` has webp variants at 32/64/128/256/512px
804
+ * widths (never wider than the original): use the smallest one that covers
805
+ * your render size, or `original` if none does.
1234
806
  */
1235
- type OfferEditorialDto = {
1236
- tagline: string;
1237
- banner_url: string;
1238
- starts_at: string;
807
+ type TokenLogo = {
808
+ kind: 'VECTOR';
809
+ url: TokenLogoUrl;
810
+ } | {
811
+ kind: 'RASTER';
812
+ original: TokenLogoImage;
813
+ variants: TokenLogoImage[];
1239
814
  };
1240
815
  /**
1241
- * What an Ondo swap sends in place of {@link OfferEditorialDto}. It trades
1242
- * continuously and runs no campaign, so the keys arrive as `null` and carry
1243
- * nothing. Declared so a fixture can describe the real payload; no `fromDto`
1244
- * reads them.
816
+ * Display metadata for one token, from CoinList's public token registry.
817
+ *
818
+ * Keyed by `(chain, address)` — never by symbol, which can collide. The
819
+ * registry is curated: a token missing from it is an expected answer, not an
820
+ * error, so lookups return `null` rather than throwing.
1245
821
  */
1246
- type OfferNoEditorialDto = {
1247
- tagline: null;
1248
- banner_url: null;
1249
- starts_at: null;
1250
- };
1251
- type CoinListTokenSaleOfferDto = OfferBaseDto & OfferEditorialDto & {
1252
- type: 'coinlist::token_sale';
1253
- logo_url: string;
1254
- };
1255
- type SuperstateSwapOfferDto = OfferBaseDto & OfferEditorialDto & {
1256
- type: 'superstate::swap';
1257
- /** Publishing a swap does not require anyone to upload artwork for it. */
1258
- logo_url: string | null;
1259
- };
1260
- type OndoSwapOfferDto = OfferBaseDto & OfferNoEditorialDto & {
1261
- type: 'ondo::swap';
1262
- /** Publishing a swap does not require anyone to upload artwork for it. */
1263
- logo_url: string | null;
1264
- };
1265
- type OfferTokenDto = {
1266
- role: 'funding' | 'distribution' | 'swap';
1267
- chain: string;
1268
- address: string;
822
+ type TokenMetadata = {
823
+ identifier: TokenIdentifier;
824
+ name: string;
825
+ symbol: AssetSymbol;
826
+ decimals: AssetDecimals;
827
+ logo: TokenLogo;
828
+ /** Dark-theme logo; `null` when the registry configures none. */
829
+ logoDark: TokenLogo | null;
1269
830
  };
1270
831
 
1271
- type OfferId = Newtype<string, 'OfferId'>;
1272
- declare const OfferId: (value: string) => OfferId;
1273
- type OfferSlug = Newtype<string, 'OfferSlug'>;
1274
- declare const OfferSlug: (value: string) => OfferSlug;
1275
- type OfferType = OfferTypeDto;
1276
- declare const isOfferType: (value: string) => value is OfferType;
1277
- type OfferBase = {
1278
- id: OfferId;
1279
- slug: OfferSlug;
1280
- endsAt: Date | null;
1281
- tokens: OfferToken[];
1282
- };
1283
- type CoinListTokenSaleOffer = OfferBase & OfferEditorial & {
1284
- type: 'coinlist::token_sale';
1285
- logoUrl: string;
1286
- };
1287
- type SuperstateSwapOffer = OfferBase & OfferEditorial & {
1288
- type: 'superstate::swap';
1289
- /** Null when nobody has uploaded artwork: a swap publishes without it. */
1290
- logoUrl: string | null;
1291
- };
1292
- type OndoSwapOffer = OfferBase & {
1293
- type: 'ondo::swap';
1294
- /** Null when nobody has uploaded artwork: a swap publishes without it. */
1295
- logoUrl: string | null;
832
+ /**
833
+ * One chain a vault offer runs on, with everything a screen needs to read and
834
+ * show a position there: the proxy every request and claim goes through, the
835
+ * share token it issues, and the funding token it is paid in.
836
+ *
837
+ * The share token's `symbol` is the one amounts are labelled with. Vault
838
+ * routes are keyed by the offer's asset code, from `centrifugeShareSymbol`.
839
+ *
840
+ * @alpha
841
+ */
842
+ type CentrifugeVault = {
843
+ chain: EthereumChain;
844
+ proxy: EvmContractAddress;
845
+ share: TokenMetadata;
846
+ funding: TokenMetadata;
1296
847
  };
1297
848
  /**
1298
- * A curated sale window and the hand-authored copy that goes with it. Every
1299
- * variant that runs such a campaign carries all three, validated; an Ondo swap
1300
- * trades continuously, runs none, and carries none.
849
+ * The vaults an offer runs on, or why a chain could not be resolved: one
850
+ * entry per `vaultContracts` chain, in the offer's order.
851
+ *
852
+ * @alpha
1301
853
  */
1302
- type OfferEditorial = {
1303
- tagline: string;
1304
- bannerUrl: string;
1305
- startsAt: Date;
854
+ type CentrifugeVaultResolution = {
855
+ type: 'resolved';
856
+ vault: CentrifugeVault;
857
+ } | {
858
+ type: 'unresolved';
859
+ chain: OfferToken['chain'];
860
+ /** Which token the offer or the registry could not name. */
861
+ missing: 'share-token' | 'funding-token' | 'evm-chain';
1306
862
  };
1307
- declare const OfferEditorial: {
863
+ /**
864
+ * Resolves each chain of a vault offer to its share and funding tokens.
865
+ *
866
+ * The offer names tokens by role and address only - the share token as
867
+ * `vault`, the coin it is paid in as `funding` - so `lookup` turns each into
868
+ * metadata. A function rather than a registry snapshot, so this module does
869
+ * not depend on where the metadata comes from.
870
+ *
871
+ * A chain whose tokens cannot be named is reported rather than dropped: its
872
+ * position cannot be read without the symbol, and a caller has to know it is
873
+ * not showing it. When a chain lists several funding tokens, the first one
874
+ * `lookup` knows wins - a vault is priced in one.
875
+ *
876
+ * @alpha
877
+ */
878
+ declare function resolveCentrifugeVaults(offer: CentrifugeVaultOfferDetail, lookup: (token: OfferToken) => TokenMetadata | null): CentrifugeVaultResolution[];
879
+
880
+ /** @alpha */
881
+ type GetCentrifugeVaultStatusParams = {
882
+ /** Share token symbol of the vault, e.g. `deJTRSY`. */
883
+ symbol: AssetSymbol;
1308
884
  /**
1309
- * `model` prefixes the field in the error (`'Offer'`, `'OfferDetail'`), so
1310
- * the same three checks name the payload that failed them.
885
+ * The chain whose proxy answers. Only chains this offer has a proxy on
886
+ * are accepted; the offer's `vault_contracts` lists them.
1311
887
  */
1312
- fromDto: (dto: OfferEditorialDto, model: string) => OfferEditorial;
888
+ chain: EthereumChain;
1313
889
  };
1314
890
  /**
1315
- * An offer as `GET /v1/offers` lists it: a union on {@link OfferType}, each
1316
- * variant carrying only the fields that mean something for it. Narrow on
1317
- * `type` before reading anything beyond {@link OfferBase} - an Ondo swap has
1318
- * no `startsAt` to read, and the compiler says so.
891
+ * One wallet on one vault: what every position read and every claim is keyed
892
+ * by. There is no order id - a wallet has one pooled position per vault per
893
+ * direction.
894
+ *
895
+ * @alpha
1319
896
  */
1320
- type Offer = CoinListTokenSaleOffer | SuperstateSwapOffer | OndoSwapOffer;
1321
- declare const Offer: {
1322
- fromDto: (dto: OfferDto) => Offer;
897
+ type CentrifugeWalletParams = GetCentrifugeVaultStatusParams & {
1323
898
  /**
1324
- * Maps a page of offers, dropping every offer whose `type` this SDK does not
1325
- * name. The same reasoning as {@link OfferToken.listFromDto}: a plain `.map`
1326
- * would throw out of `fromDto` and, through `PaginatedResponse.fromDto`,
1327
- * reject the whole page, so one offer of an unknown type would blank the
1328
- * grid for every offer beside it. A single offer fetched by id still
1329
- * throws, because there is nothing beside it to keep.
899
+ * The wallet whose position is read, or claimed for. A claim must then be
900
+ * broadcast from this same wallet: the proxy credits the sender.
1330
901
  */
1331
- listFromDto: (dtos: OfferDto[]) => Offer[];
902
+ walletAddress: EvmWalletAddress;
1332
903
  };
1333
- type TokenRole = 'funding' | 'distribution' | 'swap';
1334
- type OfferToken = {
1335
- role: TokenRole;
1336
- chain: Chain;
1337
- address: EvmContractAddress;
1338
- };
1339
- declare const OfferToken: {
1340
- fromDto: (dto: OfferTokenDto) => OfferToken;
1341
- /**
1342
- * Maps an offer's token list, dropping every token on a chain this SDK does
1343
- * not name. Frontline's chain registry runs ahead of {@link Chain}, and a
1344
- * plain `.map` would throw out of `Offer.fromDto` and, through
1345
- * `PaginatedResponse.fromDto`, reject the whole offers page - which is how
1346
- * one Base token blanked production's offers list. Dropping costs that
1347
- * token's metadata alone: `coinlist.tokens.get` takes an `EthereumChain`,
1348
- * so nobody could have looked it up anyway.
1349
- *
1350
- * Same shape as `TokenMetadata.fromRegistryDto`.
1351
- */
1352
- listFromDto: (dtos: OfferTokenDto[]) => OfferToken[];
1353
- };
1354
-
1355
904
  /**
1356
- * The cursor-paginated envelope every list endpoint returns.
905
+ * A chain read of one wallet on one vault. The vault is the resolved one:
906
+ * its proxy is what the views live on, and its tokens' decimals scale the
907
+ * amounts, so neither is read again.
1357
908
  *
1358
- * Generic in its item type, so each resource pairs it with its own item DTO
1359
- * rather than declaring an envelope of its own.
1360
- */
1361
- interface PaginatedResponseDto<T> {
1362
- data: T[];
1363
- starting_after?: string;
1364
- starting_before?: string;
1365
- }
1366
-
1367
- type Cursor = Newtype<string, 'Cursor'>;
1368
- declare const Cursor: (value: string) => Cursor;
1369
- interface PaginatedResponse<T> {
1370
- data: T[];
1371
- startingAfter: Cursor | null;
1372
- startingBefore: Cursor | null;
1373
- }
1374
- declare const PaginatedResponse: {
1375
- fromDto: <A, B>(dto: PaginatedResponseDto<A>, itemMapper: (item: A) => B) => PaginatedResponse<B>;
1376
- /**
1377
- * For a mapper that decides the page as a whole - one that drops items it
1378
- * cannot map, such as `Offer.listFromDto` - rather than item by item. The
1379
- * cursors are the wire's either way: dropping an item changes what the
1380
- * page holds, not where the next page starts.
1381
- */
1382
- fromDtoPage: <A, B>(dto: PaginatedResponseDto<A>, pageMapper: (items: A[]) => B[]) => PaginatedResponse<B>;
1383
- };
1384
- /**
1385
- * Cursor-based pagination input used when requesting paginated API resources.
1386
- * Set `after` or `before` to navigate relative to a known cursor, and `limit`
1387
- * to control the maximum number of returned items.
1388
- */
1389
- interface PaginationParams {
1390
- before?: Cursor;
1391
- after?: Cursor;
1392
- limit?: number;
1393
- }
1394
- declare const PaginationParams: {
1395
- toQueryParams: (params: PaginationParams) => Record<string, QueryParamValue>;
1396
- };
1397
-
1398
- type AssetDto = {
1399
- code: string;
1400
- fractional_digits: number;
1401
- id: string;
1402
- name: string;
1403
- };
1404
-
1405
- type AssetId = Newtype<string, 'AssetId'>;
1406
- declare const AssetId: (value: string) => AssetId;
1407
- type AssetCode = Newtype<string, 'AssetCode'>;
1408
- declare const AssetCode: (value: string) => AssetCode;
1409
- type Asset = {
1410
- id: AssetId;
1411
- code: AssetCode;
1412
- name: string;
1413
- fractionalDigits: number;
1414
- };
1415
- declare const Asset: {
1416
- fromDto: (dto: AssetDto) => Asset;
1417
- };
1418
-
1419
- type ParticipationStatusDto = 'prepared' | 'pending' | 'submitted' | 'completed' | 'failed' | 'remit_submitted' | 'remitted' | 'remit_failed';
1420
- type ParticipationDto = {
1421
- object: 'participation';
1422
- id: string;
1423
- offer_id: string;
1424
- offer_option_id: string;
1425
- status: ParticipationStatusDto;
1426
- amount: string;
1427
- amount_string: string;
1428
- asset: AssetDto;
1429
- chain: string;
1430
- inserted_at: string | null | undefined;
1431
- updated_at: string | null | undefined;
1432
- wallet_address: string | null | undefined;
1433
- };
1434
- type CreateParticipationDto = {
1435
- offer_id: string;
1436
- offer_option_id: string;
1437
- chain: string;
1438
- wallet_address: string;
1439
- amount: string;
1440
- asset_id: string;
1441
- approval_transaction_hash: string | null | undefined;
1442
- };
1443
-
1444
- /**
1445
- * One variant per `OfferTypeDto`, the same split as `OfferDto` with what
1446
- * `GET /v1/offers/:id` adds on top. Read `type` first.
909
+ * @alpha
1447
910
  */
1448
- type OfferDetailDto = CoinListTokenSaleOfferDetailDto | SuperstateSwapOfferDetailDto | OndoSwapOfferDetailDto;
1449
- type CoinListTokenSaleOfferDetailDto = OfferDetailBaseDto & OfferEditorialDto & OfferDetailNoSwapContractsDto & {
1450
- type: 'coinlist::token_sale';
1451
- logo_url: string;
1452
- };
1453
- type SuperstateSwapOfferDetailDto = OfferDetailBaseDto & OfferEditorialDto & OfferDetailNoSwapContractsDto & {
1454
- type: 'superstate::swap';
1455
- logo_url: string | null;
1456
- };
1457
- type OndoSwapOfferDetailDto = OfferDetailBaseDto & OfferNoEditorialDto & {
1458
- type: 'ondo::swap';
1459
- logo_url: string | null;
1460
- /**
1461
- * Every chain this offer swaps on, with the contract each settles
1462
- * through. `[]` is an answer - "swaps nowhere" - and a missing key is not.
1463
- */
1464
- swap_contracts: OfferDetailSwapContractDto[];
911
+ type ReadCentrifugePositionParams = {
912
+ vault: CentrifugeVault;
913
+ walletAddress: EvmWalletAddress;
1465
914
  };
1466
915
  /**
1467
- * An offer that does not settle through a CoinList swap contract sends `[]`
1468
- * or no key at all, and either way there is nothing to read. Declared so a
1469
- * fixture can describe the real payload.
916
+ * What `POST /v1/centrifuge/vault/invest` is asked for: the wallet that will
917
+ * broadcast the request and fund it, plus the deposit.
918
+ *
919
+ * Built once, when the user confirms an amount. Broadcasting the result places
920
+ * a request; it does not buy shares.
921
+ *
922
+ * @alpha
1470
923
  */
1471
- type OfferDetailNoSwapContractsDto = {
1472
- swap_contracts?: OfferDetailSwapContractDto[];
1473
- };
1474
- type OfferDetailBaseDto = {
1475
- asset: AssetDto;
1476
- faqs: OfferDetailFaqDto[];
1477
- funding_assets: AssetDto[];
1478
- tokens: OfferTokenDto[];
1479
- id: string;
1480
- links: OfferDetailLinkDto[];
1481
- milestones: OfferDetailMilestoneDto[];
1482
- name: string;
1483
- object: 'offer_details';
1484
- options: OfferDetailOptionDto[];
1485
- slug: string;
1486
- terms: OfferDetailTermDto[];
1487
- about: string | null | undefined;
1488
- category: string;
1489
- ends_at: string | null;
1490
- /** Issuer of the *token*, not of the security it tracks. Ondo offers only. */
1491
- issuer: string | null;
1492
- /** ISO 6166 id of the underlying security. Null when the supplier has none. */
1493
- isin: string | null;
1494
- /** The underlying's symbol on its venue (`QQQ`), never the token's (`QQQon`). */
1495
- ticker: string | null;
1496
- /** Supplier vocabulary (`Stock`, `ETF`). Open set — display, do not branch. */
1497
- instrument_type: string | null;
1498
- /** Venue the underlying lists on. Not served by the supplier; entered by hand. */
1499
- listing_venue: string | null;
1500
- };
1501
- type OfferDetailFaqDto = {
1502
- answer: string | null;
1503
- question: string | null;
1504
- };
1505
- type OfferDetailLinkDto = {
1506
- label: string | null;
1507
- url: string | null;
1508
- };
1509
- type OfferDetailMilestoneDto = {
1510
- name: string | null;
1511
- schedule: string | null;
1512
- status: 'completed' | 'active' | 'upcoming';
1513
- };
1514
- type OfferDetailOptionDto = {
1515
- bid_increment: number | null;
1516
- floor_price_usd: number | null;
1517
- id: string;
1518
- minimum_purchase_usd: number | null;
1519
- price_usd: string | null;
1520
- sale_agreement_url: string | null;
1521
- slug: string;
1522
- total_token_supply: number | null;
1523
- };
1524
- type OfferDetailTermDto = {
1525
- key: string | null;
1526
- value: string | null;
1527
- };
1528
- type OfferDetailSwapContractDto = {
1529
- chain: string;
1530
- address: string;
924
+ type BuildCentrifugeVaultBuyParams = CentrifugeWalletParams & {
925
+ /** How much of the funding token to deposit, in its base units. */
926
+ amount: BlockchainAmount;
1531
927
  };
1532
928
 
1533
- type Ticker = Newtype<string, 'Ticker'>;
1534
- declare const Ticker: (value: string) => Ticker;
1535
929
  /**
1536
- * An ISO 6166 International Securities Identification Number, e.g.
1537
- * `US0378331005`. Identifies the security itself, which is what makes it the
1538
- * stable key across venues where a {@link Ticker} is not: the same security
1539
- * trades under different tickers in different places, and the same ticker
1540
- * means different securities in different places.
930
+ * Centrifuge vault reads and claims: the vault-wide ordering gate, each
931
+ * wallet's position per direction, and the calldata that claims a filled
932
+ * request.
1541
933
  *
1542
- * Not validated on construction — the value comes from a supplier that has
1543
- * already checked it, and a client-side check-digit test could only reject
1544
- * data the SDK still has to display.
934
+ * The reads are free to poll - none spends an attestation, so a client may
935
+ * call them while the user sizes a deposit or waits on a fill.
936
+ *
937
+ * A vault trade settles in two legs: a request the fund fills on its own
938
+ * schedule, then a claim. There is no order id - each wallet has one pooled
939
+ * position per vault per direction - so the claims take a wallet, not an
940
+ * order, and collect everything claimable.
941
+ *
942
+ * The calldata builders are the exception: {@link CentrifugeNamespace.buildBuyTransaction}
943
+ * and the claims are each called once, when the user confirms, and never on a
944
+ * timer. Broadcasting the buy calldata places a request. It does not buy
945
+ * shares.
946
+ *
947
+ * Every Frontline method requires `AuthState === 'logged-in'` and throws
948
+ * {@link NotAuthenticatedError} otherwise.
949
+ * {@link CentrifugeNamespace.readDepositPosition} and
950
+ * {@link CentrifugeNamespace.readRedeemPosition} read the proxy over RPC and
951
+ * do not; through CoinList's RPC proxy a logged-out read fails with its 401.
952
+ *
953
+ * @alpha
1545
954
  */
1546
- type Isin = Newtype<string, 'Isin'>;
1547
- declare const Isin: (value: string) => Isin;
1548
- type OrderBookSide = 'buy' | 'sell';
1549
-
1550
- type CoinListTokenSaleOfferDetail = OfferDetailBase & OfferEditorial & {
1551
- type: 'coinlist::token_sale';
1552
- logoUrl: string;
1553
- };
1554
- type SuperstateSwapOfferDetail = OfferDetailBase & OfferEditorial & {
1555
- type: 'superstate::swap';
1556
- /** Null when nobody has uploaded artwork: a swap publishes without it. */
1557
- logoUrl: string | null;
1558
- };
1559
- type OndoSwapOfferDetail = OfferDetailBase & {
1560
- type: 'ondo::swap';
1561
- /** Null when nobody has uploaded artwork: a swap publishes without it. */
1562
- logoUrl: string | null;
1563
- /**
1564
- * Every chain this offer can be swapped on, with the CoinList contract each
1565
- * settles through. Empty means it cannot be swapped anywhere.
1566
- */
1567
- swapContracts: OfferSwapContract[];
1568
- };
1569
- type OfferDetailBase = {
1570
- id: OfferId;
1571
- slug: OfferSlug;
1572
- name: string;
1573
- asset: Asset;
1574
- fundingAssets: Asset[];
1575
- tokens: OfferToken[];
1576
- about: string | null;
1577
- category: string;
955
+ interface CentrifugeNamespace {
956
+ getVaultStatus(params: GetCentrifugeVaultStatusParams): Promise<CentrifugeVaultStatus>;
1578
957
  /**
1579
- * The legal issuer of the *token*, e.g. `'Ondo Global Markets'`. The issuer
1580
- * of the security it tracks is {@link OfferDetail.name} (`'Apple Inc.'`).
1581
- * Null on offers with no single named issuer.
958
+ * The wallet's Deposit (buy) standing on the vault: funding token still
959
+ * queued, and funding token filled and ready to claim. A wallet that has
960
+ * never touched the vault reads as zeroes.
1582
961
  */
1583
- issuer: string | null;
962
+ readDepositPosition(params: ReadCentrifugePositionParams): Promise<CentrifugeDepositPosition>;
1584
963
  /**
1585
- * ISO 6166 identifier of the underlying security, e.g. `'US0378331005'`.
1586
- * Null both on offers that tokenize no security and on the few that do but
1587
- * whose supplier publishes no ISIN, so absence does not imply "not a
1588
- * security".
964
+ * The wallet's Redeem (sell) standing on the vault: shares still queued,
965
+ * and shares filled and ready to claim.
1589
966
  */
1590
- isin: Isin | null;
967
+ readRedeemPosition(params: ReadCentrifugePositionParams): Promise<CentrifugeRedeemPosition>;
1591
968
  /**
1592
- * The underlying security's symbol on its listing venue, e.g. `'QQQ'`. This
1593
- * is *not* the token's symbol — that is `asset.code` (`'QQQon'`). Null on
1594
- * offers that tokenize no listed security.
969
+ * Calldata claiming every claimable share. Broadcast it from
970
+ * `params.walletAddress`: the proxy credits the sender.
1595
971
  */
1596
- ticker: Ticker | null;
972
+ createClaim(params: CentrifugeWalletParams): Promise<CentrifugeVaultClaimTransaction>;
1597
973
  /**
1598
- * What kind of instrument the underlying is, as the supplier classifies it:
1599
- * `'Stock'`, `'ETF'`. Distinct from {@link OfferDetail.category}, which is
1600
- * the broader class the offer is grouped under (`'Equities'`) — this field
1601
- * describes the single instrument, `category` describes the shelf it sits
1602
- * on.
974
+ * Calldata paying out every claimable redemption in the funding token.
975
+ * Broadcast it from `params.walletAddress`.
1603
976
  *
1604
- * An open set: the vocabulary belongs to the supplier and may grow without
1605
- * notice, so render an unrecognized value rather than branching on it. Note
1606
- * the supplier's spelling is its own — a common stock arrives as `'Stock'`.
977
+ * TODO(frontline, ENG-1909): built against a guessed endpoint.
1607
978
  */
1608
- instrumentType: string | null;
979
+ createClaimProceeds(params: CentrifugeWalletParams): Promise<CentrifugeVaultClaimProceedsTransaction>;
1609
980
  /**
1610
- * The exchange the underlying security lists on, e.g. `'NASDAQ'`. Not
1611
- * derivable from {@link OfferDetail.isin}, whose country prefix is the
1612
- * country of issuance rather than the venue.
981
+ * Builds the calldata that places a deposit request.
1613
982
  *
1614
- * Unlike the fields above, this one is recorded by hand rather than served
1615
- * by the supplier, so it is null until someone records it — on a newly
1616
- * listed offer that is the normal state, and it says nothing about whether
1617
- * the offer tokenizes a security.
983
+ * Called once on confirmation. The reads above are the ones a checkout polls.
1618
984
  */
1619
- listingVenue: string | null;
1620
- endsAt: Date | null;
1621
- faqs: FaqItem[];
1622
- links: Link[];
1623
- milestones: Milestone[];
1624
- options: OfferOption[];
1625
- terms: TermItem[];
1626
- };
1627
- type OfferOptionId = Newtype<string, 'OfferOptionId'>;
1628
- declare const OfferOptionId: (value: string) => OfferOptionId;
1629
- type OfferOptionSlug = Newtype<string, 'OfferOptionSlug'>;
1630
- declare const OfferOptionSlug: (value: string) => OfferOptionSlug;
1631
- /**
1632
- * A chain an offer can be swapped on, with the CoinList contract the swap
1633
- * settles through - the spender to approve before any quote exists.
1634
- *
1635
- * Carries no role, unlike {@link OfferToken}: there is one swap contract per
1636
- * chain and it serves every Ondo asset on it, so which token is being traded
1637
- * does not change the answer.
1638
- */
1639
- type OfferSwapContract = {
1640
- chain: Chain;
1641
- address: EvmContractAddress;
1642
- };
1643
- declare const OfferSwapContract: {
1644
- fromDto: (dto: OfferDetailSwapContractDto) => OfferSwapContract;
1645
- };
985
+ buildBuyTransaction(params: BuildCentrifugeVaultBuyParams): Promise<CentrifugeVaultBuyTransaction>;
986
+ }
987
+
988
+ type Cursor = Newtype<string, 'Cursor'>;
989
+ declare const Cursor: (value: string) => Cursor;
1646
990
  /**
1647
- * An offer as `GET /v1/offers/:id` serves it: the same union on `type` as
1648
- * {@link Offer}, each variant carrying only the fields that mean something
1649
- * for it. Narrow on `type` before reading anything beyond
1650
- * {@link OfferDetailBase}.
991
+ * Cursor-based pagination input used when requesting paginated API resources.
992
+ * Set `after` or `before` to navigate relative to a known cursor, and `limit`
993
+ * to control the maximum number of returned items.
1651
994
  */
1652
- type OfferDetail = CoinListTokenSaleOfferDetail | SuperstateSwapOfferDetail | OndoSwapOfferDetail;
1653
- declare const OfferDetail: {
1654
- fromDto: (dto: OfferDetailDto) => OfferDetail;
1655
- };
1656
- type OfferOption = {
1657
- id: OfferOptionId;
1658
- slug: OfferOptionSlug;
1659
- bidIncrement: number | null;
1660
- floorPriceUsd: number | null;
1661
- minimumPurchaseUsd: number | null;
1662
- priceUsd: string | null;
1663
- saleAgreementUrl: string | null;
1664
- totalTokenSupply: number | null;
1665
- };
1666
- declare const OfferOption: {
1667
- fromDto: (dto: OfferDetailOptionDto) => OfferOption;
1668
- };
1669
- type FaqItem = {
1670
- question: string | null;
1671
- answer: string | null;
1672
- };
1673
- declare const FaqItem: {
1674
- fromDto: (dto: OfferDetailFaqDto) => FaqItem;
1675
- };
1676
- type Link = {
1677
- label: string | null;
1678
- url: string | null;
1679
- };
1680
- declare const Link: {
1681
- fromDto: (dto: OfferDetailLinkDto) => Link;
1682
- };
1683
- type TermItem = {
1684
- key: string | null;
1685
- value: string | null;
1686
- };
1687
- declare const TermItem: {
1688
- fromDto: (dto: OfferDetailTermDto) => TermItem;
1689
- };
1690
- type Milestone = {
1691
- name: string | null;
1692
- schedule: string | null;
1693
- status: 'completed' | 'active' | 'upcoming';
1694
- };
1695
- declare const Milestone: {
1696
- fromDto: (dto: OfferDetailMilestoneDto) => Milestone;
1697
- };
995
+ interface PaginationParams {
996
+ before?: Cursor;
997
+ after?: Cursor;
998
+ limit?: number;
999
+ }
1000
+ interface PaginatedResponse<T> {
1001
+ data: T[];
1002
+ startingAfter: Cursor | null;
1003
+ startingBefore: Cursor | null;
1004
+ }
1698
1005
 
1699
1006
  /** Unique identifier for a participation. */
1700
1007
  type ParticipationId = Newtype<string, 'ParticipationId'>;
@@ -1709,14 +1016,7 @@ type WalletAddress = Newtype<`0x${string}`, 'WalletAddress'>;
1709
1016
  /** Casts a `0x`-prefixed string into a typed {@link WalletAddress}. */
1710
1017
  declare const WalletAddress: (value: `0x${string}`) => WalletAddress;
1711
1018
  /** Possible participation lifecycle states returned by the API. */
1712
- type ParticipationStatus = ParticipationStatusDto;
1713
- /** Pagination params for listing participations, with an optional offer filter. */
1714
- interface ParticipationsPaginationParams extends PaginationParams {
1715
- offerId?: OfferId;
1716
- }
1717
- declare const ParticipationsPaginationParams: {
1718
- toQueryParams: (params: ParticipationsPaginationParams) => Record<string, QueryParamValue>;
1719
- };
1019
+ type ParticipationStatus = 'prepared' | 'pending' | 'submitted' | 'completed' | 'failed' | 'remit_submitted' | 'remitted' | 'remit_failed';
1720
1020
  /** Domain model for a participation returned by CoinList APIs. */
1721
1021
  type Participation = {
1722
1022
  /** Unique participation id. */
@@ -1742,10 +1042,6 @@ type Participation = {
1742
1042
  /** Wallet used for participation, blank values normalized to null. */
1743
1043
  walletAddress: WalletAddress | null;
1744
1044
  };
1745
- declare const Participation: {
1746
- /** Maps API DTO shape into the SDK participation domain model. */
1747
- fromDto: (dto: ParticipationDto) => Participation;
1748
- };
1749
1045
  /** Parameters required to create a new participation. */
1750
1046
  type CreateParticipationParams = {
1751
1047
  /** Offer to participate in. */
@@ -1771,10 +1067,10 @@ type CreateParticipationParams = {
1771
1067
  */
1772
1068
  approvalTransactionHash: string;
1773
1069
  };
1774
- declare const CreateParticipationParams: {
1775
- /** Maps participation creation params into API DTO payload. */
1776
- toDto: (params: CreateParticipationParams) => CreateParticipationDto;
1777
- };
1070
+ /** Pagination params for listing participations, with an optional offer filter. */
1071
+ interface ParticipationsPaginationParams extends PaginationParams {
1072
+ offerId?: OfferId;
1073
+ }
1778
1074
 
1779
1075
  /**
1780
1076
  * Read/write operations for token sales: listing and reading the current user's
@@ -1812,377 +1108,6 @@ interface CoinListTokenSaleNamespace {
1812
1108
  */
1813
1109
  createParticipation(params: CreateParticipationParams): Promise<Participation>;
1814
1110
  }
1815
- declare class CoinListTokenSaleNamespaceImpl implements CoinListTokenSaleNamespace {
1816
- private readonly ctx;
1817
- protected readonly log: InternalLogger;
1818
- constructor(ctx: UniversalNamespaceContext);
1819
- list(offerId?: OfferId): Promise<Participation[]>;
1820
- listPage(params: ParticipationsPaginationParams): Promise<PaginatedResponse<Participation>>;
1821
- get(id: ParticipationId): Promise<Participation>;
1822
- createParticipation(params: CreateParticipationParams): Promise<Participation>;
1823
- }
1824
-
1825
- /**
1826
- * No `chain` on the read params: Ondo runs no sandbox, so every environment
1827
- * prices against Ondo production on Ethereum mainnet. Accepting a chain would
1828
- * let a caller ask for Sepolia and silently receive mainnet pricing.
1829
- * {@link BuildOndoSwapTransactionParams} is the exception, and says why.
1830
- */
1831
- type GetOndoTradingStatusParams = {
1832
- symbol: AssetSymbol;
1833
- /**
1834
- * Required, and deliberately not defaulted: `tradable` and both order caps
1835
- * describe this side only. Guessing `buy` would hand buy caps to someone
1836
- * sizing a sell. The endpoint answers 422 without it.
1837
- */
1838
- side: OrderBookSide;
1839
- };
1840
- /** How long Ondo should hold the price. Omit to take Ondo's own default. */
1841
- type OndoQuoteDuration = 'short' | 'long';
1842
- /**
1843
- * A quote is sized by the quantity or by the dollar amount. The endpoint takes
1844
- * exactly one; `getOndoQuote` sends whichever is present.
1845
- */
1846
- type OndoQuoteSize = {
1847
- tokenAmount: BlockchainAmount;
1848
- } | {
1849
- notionalValue: DecimalString;
1850
- };
1851
- type GetOndoQuoteParams = {
1852
- symbol: AssetSymbol;
1853
- side: OrderBookSide;
1854
- duration?: OndoQuoteDuration;
1855
- } & OndoQuoteSize;
1856
- /**
1857
- * What both builders take, before the one field whose meaning forks.
1858
- *
1859
- * There is no funding token and no asset here - frontline resolves both from
1860
- * the offer, because Ninshubur signs a request bound to them and a caller that
1861
- * could name them could have CoinList sign for a contract of its own. Which
1862
- * endpoint was called is what says which of the two `amount` counts.
1863
- *
1864
- * `chain` is required although the read params refuse it, because these name a
1865
- * real contract on a real chain rather than asking Ondo for a price.
1866
- */
1867
- type BuildOndoSwapParamsCore = {
1868
- symbol: AssetSymbol;
1869
- chain: EthereumChain;
1870
- /**
1871
- * The wallet that will *send* the transaction, and that receives the other
1872
- * token. The calldata is signed over it, so a transaction built for one
1873
- * wallet and broadcast by another reverts.
1874
- */
1875
- walletAddress: EvmWalletAddress;
1876
- };
1877
- /**
1878
- * What it takes to turn an indicative price into signed, fillable calldata for
1879
- * a purchase: `POST /v1/ondo/swap/buy`.
1880
- *
1881
- * Separate from {@link BuildOndoSellParams} although the fields match today,
1882
- * because `amount` is denominated in a different token on each - which is
1883
- * frontline's own reason for splitting the endpoint rather than taking a
1884
- * `side`. Sharing one type would re-merge the distinction the split exists to
1885
- * make, and the two will diverge the day either side gains a knob.
1886
- */
1887
- type BuildOndoBuyParams = BuildOndoSwapParamsCore & {
1888
- /**
1889
- * The **gross** deposit, in the base units of the funding token - the coin
1890
- * the user chose and approved.
1891
- *
1892
- * Sized by `amount` alone, with no `notionalValue` alternative: the calldata
1893
- * authorises a specific ERC-20 pull, so the number that ends up on chain has
1894
- * to be the number the caller meant rather than one derived from a dollar
1895
- * figure. CoinList's fee comes off it, and Ondo prices the remainder.
1896
- *
1897
- * Its `decimals` are also what the response's `spend_input_decimals` is
1898
- * checked against: frontline resolves the funding token from the offer
1899
- * rather than from this request, so the two are independent answers to the
1900
- * same question and a disagreement means the wrong token was sized.
1901
- */
1902
- amount: BlockchainAmount;
1903
- };
1904
- /**
1905
- * What it takes to turn an indicative price into signed, fillable calldata for
1906
- * a sale: `POST /v1/ondo/swap/sell`.
1907
- *
1908
- * See {@link BuildOndoBuyParams} for why this is its own type.
1909
- */
1910
- type BuildOndoSellParams = BuildOndoSwapParamsCore & {
1911
- /**
1912
- * The quantity of the **asset** to sell, in the asset's own base units - not
1913
- * in the funding token's, which is the same integer meaning something 1e12
1914
- * different.
1915
- *
1916
- * This is the figure the wallet must have approved: a sale delivers the
1917
- * asset, so the swap contract pulls it with `transferFrom` exactly as it
1918
- * pulls the deposit on a purchase.
1919
- *
1920
- * Its `decimals` are checked against the response's `spend_input_decimals`,
1921
- * which frontline reads on-chain from the asset on `chain`. The SDK's own
1922
- * answer comes from the quote, which resolves the asset on Ethereum mainnet,
1923
- * so on a testnet these are two independent resolutions of two different
1924
- * contracts - and the check is what says so.
1925
- */
1926
- amount: BlockchainAmount;
1927
- };
1928
-
1929
- /**
1930
- * Raw JSON models for the Ondo swap endpoints, mirroring
1931
- * `OndoSwapTradingStatus`, `OndoSwapQuote`, `OndoSwapBuy` and `OndoSwapSell`
1932
- * in frontline's OpenAPI schema. The two GETs are free to poll: neither spends
1933
- * an attestation, so a client may call them while the user edits an order. The
1934
- * two POSTs are not - each spends one and hands back signed calldata with a
1935
- * deadline.
1936
- *
1937
- * **One endpoint per side, and therefore one model per side.** The reads take
1938
- * a `side` and answer the same shape either way, so they stay single. The
1939
- * writes do not: a buy commits to an exact quantity, a sell to a range with a
1940
- * floor beneath it, and `amount` is the funding token on one and the asset on
1941
- * the other. Only the transport fields mean the same thing on both.
1942
- * `POST /v1/ondo/swap/transaction` survives as a deprecated alias of the buy
1943
- * under the old `pay_input_*` names; the SDK does not call it.
1944
- *
1945
- * Neither GET takes a `chain`. Ondo runs no sandbox, so every environment
1946
- * prices against Ondo production on Ethereum mainnet. The POSTs do carry one:
1947
- * they target a real contract, which on every environment but production is
1948
- * the Sepolia one with the mocked attestation.
1949
- */
1950
- /**
1951
- * Whether an asset can be traded right now, and the caps if so.
1952
- *
1953
- * Every cap is a *human decimal* string and is nullable — Ondo answers a
1954
- * restricted `/v1/limits/*` with a 403, which frontline turns into
1955
- * `tradable: false` and three nulls rather than an error. A null cap therefore
1956
- * means **Ondo restricted the asset, not that the cap is unlimited**. The
1957
- * response carries no `asset_decimals`, so there is nothing to scale
1958
- * `gross_max_tokens` by.
1959
- *
1960
- * There is no `reason` field. A restriction surfaces only as `tradable: false`
1961
- * plus the nulls; frontline keeps Ondo's reason codes to its own admin surface
1962
- * and does not forward them.
1963
- */
1964
- type OndoTradingStatusDto = {
1965
- object: 'ondo_swap_trading_status';
1966
- /** The `side` the request asked for, echoed back. */
1967
- side: 'buy' | 'sell';
1968
- /** For `side` only — an asset can be sellable while not buyable. */
1969
- tradable: boolean;
1970
- /** Whole tokens, for `side` only. E.g. `"100.000000000000000000"`. */
1971
- gross_max_tokens: string | null;
1972
- /** USD, for `side` only. E.g. `"1234.560000000000000000"`. */
1973
- gross_max_notional_value: string | null;
1974
- /**
1975
- * USD cap for the session the market is currently in, e.g. `"200000"`.
1976
- * Unlike the two above this comes from Ondo's sideless session endpoint, so
1977
- * it applies across buys and sells together.
1978
- */
1979
- gross_max_active_notional_value: string | null;
1980
- };
1981
- /** An indicative, size- and side-aware price for an Ondo asset. */
1982
- type OndoQuoteDto = {
1983
- object: 'ondo_swap_quote';
1984
- /** EIP-155 chain id. Always `"1"`, per the no-sandbox note above. */
1985
- chain_id: string;
1986
- symbol: string;
1987
- /** Ticker of the underlying security, e.g. `"AAPL"` for `"AAPLon"`. */
1988
- ticker: string;
1989
- asset_address: string;
1990
- /** Decimals of the `asset_address` contract. Scales `token_base_units`. */
1991
- asset_decimals: number;
1992
- side: 'buy' | 'sell';
1993
- /**
1994
- * Quantity of the asset in its smallest unit, as a raw uint256 string:
1995
- * `"5000000000000000000"` is 5 tokens at 18 decimals. Map it with
1996
- * `blockchainAmountFromRawOrThrow`, not `parseBlockchainAmountOrThrow`.
1997
- *
1998
- * Not to be confused with the `token_amount` *request* parameter, which is
1999
- * the same quantity in whole tokens. Ondo names both `tokenAmount`, 1e18
2000
- * apart, so echoing one back as the other is a real hazard.
2001
- */
2002
- token_base_units: string;
2003
- /**
2004
- * USD price of one whole token as a human decimal string, e.g.
2005
- * `"225.273151158540753535"`. Already scaled, so it needs none of the
2006
- * handling `token_base_units` does.
2007
- */
2008
- price: string;
2009
- };
2010
- /**
2011
- * The transport half of a built swap, identical on both sides.
2012
- *
2013
- * `spend_input_amount` is the only key here whose *token* depends on the side,
2014
- * and `spend_input_decimals` is what says at what scale. Everything below this
2015
- * point differs, which is why the two responses are two types rather than one
2016
- * with nullable halves.
2017
- *
2018
- * There is no `object` envelope and no `side` echo. `action` says what to do
2019
- * with the body, matching `AllowWalletResponseDto`; which trade it encodes is
2020
- * settled by the endpoint that was called, so nothing on the wire has to say
2021
- * it and nothing has to be checked against it.
2022
- */
2023
- type OndoSwapDtoCore = {
2024
- action: 'broadcast_transaction';
2025
- /** The swap contract the transaction is sent to. */
2026
- to: string;
2027
- /** ABI-encoded `swap(...)` calldata. Broadcast verbatim - never re-encode it. */
2028
- data: string;
2029
- /**
2030
- * When the signed calldata stops being accepted - RFC3339, e.g.
2031
- * `"2026-08-13T23:04:12Z"`. This is Ninshubur's `expiration`, which signs
2032
- * the same instant as the EIP-712 `deadline` in the calldata, so a
2033
- * transaction broadcast after it reverts.
2034
- */
2035
- expires_at: string;
2036
- /**
2037
- * Gross amount the wallet spends, echoing the requested `amount`, in the
2038
- * smallest unit of the token this side spends - the funding token on a buy,
2039
- * the asset on a sell. The approval is compared against this.
2040
- *
2041
- * Named `pay_input_amount` on the deprecated
2042
- * `POST /v1/ondo/swap/transaction`. `pay` was accurate only while a buy was
2043
- * the sole thing this could encode: a sell delivers the asset rather than
2044
- * paying for one.
2045
- */
2046
- spend_input_amount: string;
2047
- /**
2048
- * Decimals `spend_input_amount` is counted in.
2049
- *
2050
- * Frontline reads it on-chain from the token the side spends, which it
2051
- * resolves from the offer rather than from anything the caller sent. That
2052
- * makes it the only published scale for a token the request never names -
2053
- * and an independent answer to the one the SDK derived when it sized the
2054
- * order, which is why both builders compare the two.
2055
- *
2056
- * **The same key, two scales.** It counts whichever token the side spends:
2057
- * the funding token's 6 on a buy, the asset's 18 on a sell. Worth knowing
2058
- * before integrating against both.
2059
- */
2060
- spend_input_decimals: number;
2061
- };
2062
- /**
2063
- * Signed, ready-to-broadcast calldata for a purchase, and the amounts it
2064
- * commits to: `POST /v1/ondo/swap/buy`.
2065
- *
2066
- * Unlike {@link OndoQuoteDto} this **spends an attestation**, so it is not
2067
- * pollable: one call per order, plus one per user-requested refresh. Frontline
2068
- * also reads the wallet's allowance on the funding token before asking
2069
- * Ninshubur for anything, so an unapproved wallet is refused here rather than
2070
- * reverting on chain.
2071
- *
2072
- * The quantity is attested and exact, with no floor beneath it, which is what
2073
- * separates a buy from an {@link OndoSellDto}.
2074
- *
2075
- * **Carries no identity and no price.** No `chain_id`, `symbol`, `ticker` or
2076
- * `price` - the request named the first few and frontline drops Ninshubur's
2077
- * `price` deliberately, because `GET /v1/ondo/swap/quote` already publishes
2078
- * one under that name at a different scale. Anything the UI needs beyond the
2079
- * amounts comes from that GET or from the offer.
2080
- *
2081
- * Two scales are on the wire: `fee` and `notional_value` are in
2082
- * `spend_input_decimals`, and `receive_output_amount` is in
2083
- * `receive_output_decimals`. Neither is interchangeable with the quote's
2084
- * `asset_decimals`, which answers for a different number.
2085
- */
2086
- type OndoBuyDto = OndoSwapDtoCore & {
2087
- /**
2088
- * CoinList's cut of `spend_input_amount`, in the same units. `"0"` until
2089
- * ENG-1718 turns a fee on - frontline rejects a non-zero one today.
2090
- *
2091
- * Taken at execution rather than added on top, so the wallet never approves
2092
- * more than `spend_input_amount`.
2093
- */
2094
- fee: string;
2095
- /**
2096
- * `spend_input_amount` less `fee`, in `spend_input_decimals`: the part that
2097
- * reaches Ondo, what the quantity was priced against, and what the
2098
- * signature commits to.
2099
- */
2100
- notional_value: string;
2101
- /**
2102
- * Quantity of the asset the wallet receives, in the asset's smallest unit.
2103
- * Scale it by `receive_output_decimals`, not by the funding token's and not
2104
- * by {@link OndoQuoteDto}'s `asset_decimals`.
2105
- */
2106
- receive_output_amount: string;
2107
- /**
2108
- * Decimals `receive_output_amount` is counted in.
2109
- *
2110
- * Reported by whatever priced the quantity, rather than looked up from the
2111
- * asset. That is not the same number as {@link OndoQuoteDto}'s
2112
- * `asset_decimals`, which frontline resolves from its own catalogue: the two
2113
- * are allowed to disagree, and only this one answers for the quantity in
2114
- * this response.
2115
- */
2116
- receive_output_decimals: number;
2117
- };
2118
- /**
2119
- * Signed, ready-to-broadcast calldata for a sale, and the range it commits to:
2120
- * `POST /v1/ondo/swap/sell`.
2121
- *
2122
- * Like {@link OndoBuyDto} it **spends an attestation** and expires, so it is
2123
- * called once on confirmation and never on a timer. Frontline reads the
2124
- * wallet's allowance on the **asset** before signing - a sell delivers it -
2125
- * and refuses a short one with a 422 naming that address.
2126
- *
2127
- * **A sell commits to a range rather than a quantity.** Ondo settles through
2128
- * USDon before converting to the settlement token, so the response publishes
2129
- * what to expect and the floor the calldata enforces, each with the fee
2130
- * charged at it. There is no `notional_value`: a sell commits on the output
2131
- * side, so there is no fee-exclusive input to report and frontline declines to
2132
- * relate numbers Ninshubur did not relate.
2133
- *
2134
- * All four amounts below are in `receive_output_decimals`. The one amount that
2135
- * is not is `spend_input_amount`, in `spend_input_decimals` - see
2136
- * {@link OndoSwapDtoCore}.
2137
- *
2138
- * One thing the response does not say, and the calldata does: the floor signed
2139
- * into `data` is **gross** of CoinList's fee, so a caller decoding it finds a
2140
- * larger number than `minimum_quantity`. Both are correct; `minimum_quantity`
2141
- * is what the wallet actually receives.
2142
- */
2143
- type OndoSellDto = OndoSwapDtoCore & {
2144
- /**
2145
- * CoinList's cut at the expected outcome, in the settlement token. Already
2146
- * deducted from `expected_quantity` rather than charged on top of it.
2147
- *
2148
- * `"0"` until ENG-1718 turns a fee on - frontline rejects a non-zero one on
2149
- * either side today.
2150
- */
2151
- expected_fee: string;
2152
- /**
2153
- * What the sale is expected to return, **net of `expected_fee`**, in the
2154
- * settlement token's smallest unit.
2155
- *
2156
- * An expectation rather than a guarantee. What the contract enforces is
2157
- * {@link OndoSellDto.minimum_quantity}.
2158
- */
2159
- expected_quantity: string;
2160
- /**
2161
- * CoinList's cut at the floor, in the same units.
2162
- *
2163
- * A different number from `expected_fee` because the two are charged on
2164
- * different amounts - which is why frontline publishes both rather than one.
2165
- * This is the one to disclose worst-case cost with.
2166
- */
2167
- minimum_fee: string;
2168
- /**
2169
- * The least the wallet can receive, **net of `minimum_fee`**, in the same
2170
- * units. Below it the transaction reverts on chain.
2171
- *
2172
- * Frontline guarantees it is at most `expected_quantity` and greater than
2173
- * zero. The settlement lands somewhere between the two.
2174
- */
2175
- minimum_quantity: string;
2176
- /**
2177
- * Decimals both quantities and both fees are counted in, reported by
2178
- * whatever priced them rather than looked up from the token.
2179
- *
2180
- * Not the same number as {@link OndoQuoteDto}'s `asset_decimals`, which
2181
- * answers for the asset being sold rather than for the coin the proceeds
2182
- * arrive in.
2183
- */
2184
- receive_output_decimals: number;
2185
- };
2186
1111
 
2187
1112
  /**
2188
1113
  * Whether an Ondo asset can be traded right now.
@@ -2213,9 +1138,6 @@ type OndoTradingStatus = {
2213
1138
  type: 'not-tradable';
2214
1139
  side: OrderBookSide;
2215
1140
  };
2216
- declare const OndoTradingStatus: {
2217
- fromDto: (dto: OndoTradingStatusDto) => OndoTradingStatus;
2218
- };
2219
1141
  /**
2220
1142
  * An indicative price for an Ondo asset, free to poll while the user edits an
2221
1143
  * order.
@@ -2262,9 +1184,6 @@ type OndoQuote = {
2262
1184
  */
2263
1185
  price: DecimalString;
2264
1186
  };
2265
- declare const OndoQuote: {
2266
- fromDto: (dto: OndoQuoteDto) => OndoQuote;
2267
- };
2268
1187
  /**
2269
1188
  * The half of a built swap that both sides share: the calldata, its deadline,
2270
1189
  * and what the wallet parts with.
@@ -2349,9 +1268,6 @@ type OndoBuyTransaction = OndoSwapTransactionCore & {
2349
1268
  */
2350
1269
  receiveOutputAmount: BlockchainAmount;
2351
1270
  };
2352
- declare const OndoBuyTransaction: {
2353
- fromDto: (dto: OndoBuyDto) => OndoBuyTransaction;
2354
- };
2355
1271
  /**
2356
1272
  * One end of the range a sale commits to: what arrives, and what CoinList took
2357
1273
  * to get it there.
@@ -2407,112 +1323,230 @@ type OndoSellTransaction = OndoSwapTransactionCore & {
2407
1323
  */
2408
1324
  minimum: OndoSellOutcome;
2409
1325
  };
2410
- declare const OndoSellTransaction: {
2411
- fromDto: (dto: OndoSellDto) => OndoSellTransaction;
2412
- };
2413
1326
 
2414
1327
  /**
2415
- * Ondo swap reads, plus the write that turns one into a fillable transaction.
2416
- *
2417
- * The two reads are free to poll - neither spends an attestation, so a client
2418
- * may call them while the user edits an order. The two builders are not:
2419
- * budget one call per order placed, plus one per refresh the user asks for.
2420
- *
2421
- * **One builder per side, mirroring the endpoints.** A purchase and a sale
2422
- * agree on how to broadcast and on nothing else: a purchase commits to an
2423
- * exact quantity, a sale to a range with a floor beneath it, and `amount` is
2424
- * the funding token on one and the asset on the other. A single method taking
2425
- * a `side` would have to return a union the caller then re-narrows, having
2426
- * already decided which trade it was placing.
2427
- *
2428
- * **No CoinList fee is applied to a read quote, and no read discloses one.**
2429
- * Ondo prices exactly the amount passed. A CoinList approval is fee-inclusive,
2430
- * so sizing a quote against a user's approval without subtracting the fee
2431
- * first overstates what they receive. A built transaction carries the fee
2432
- * explicitly, and it is zero until one lands (ENG-1718).
2433
- *
2434
- * Every method requires `AuthState === 'logged-in'` and throws
2435
- * {@link NotAuthenticatedError} otherwise.
1328
+ * No `chain` on the read params: Ondo runs no sandbox, so every environment
1329
+ * prices against Ondo production on Ethereum mainnet. Accepting a chain would
1330
+ * let a caller ask for Sepolia and silently receive mainnet pricing.
1331
+ * {@link BuildOndoSwapTransactionParams} is the exception, and says why.
2436
1332
  */
2437
- interface OndoNamespace {
2438
- getTradingStatus(params: GetOndoTradingStatusParams): Promise<OndoTradingStatus>;
2439
- getQuote(params: GetOndoQuoteParams): Promise<OndoQuote>;
2440
- /**
2441
- * Builds a purchase for a specific wallet and deposit: spends an attestation
2442
- * and returns calldata to broadcast, valid until
2443
- * {@link OndoBuyTransaction.expiresAt}.
2444
- *
2445
- * The wallet must have approved the swap contract to spend `amount` of the
2446
- * offer's **funding token** first. Frontline reads that allowance before
2447
- * asking Ninshubur for anything, and rejects a short one with a 422 carrying
2448
- * `code: "insufficient_allowance"`. See `prepareBuy` on the client
2449
- * namespace, which approves and then builds, in that order.
2450
- *
2451
- * The tokens themselves are the offer's rather than the caller's to name.
2452
- */
2453
- buildBuyTransaction(params: BuildOndoBuyParams): Promise<OndoBuyTransaction>;
1333
+ type GetOndoTradingStatusParams = {
1334
+ symbol: AssetSymbol;
2454
1335
  /**
2455
- * Builds a sale for a specific wallet and quantity: spends an attestation
2456
- * and returns calldata to broadcast, valid until
2457
- * {@link OndoSellTransaction.expiresAt}.
2458
- *
2459
- * The approval this one needs is on the **asset**, not on a stablecoin - a
2460
- * sale delivers the asset, so the swap contract pulls it with `transferFrom`
2461
- * exactly as it pulls the deposit on a purchase. Same 422 when it is short,
2462
- * with a message naming that address. See `prepareSell` on the client
2463
- * namespace.
2464
- *
2465
- * Returns a range rather than a quantity: Ondo settles through USDon before
2466
- * converting to the settlement token, so disclose
2467
- * {@link OndoSellTransaction.minimum} and not only `expected`.
1336
+ * Required, and deliberately not defaulted: `tradable` and both order caps
1337
+ * describe this side only. Guessing `buy` would hand buy caps to someone
1338
+ * sizing a sell. The endpoint answers 422 without it.
2468
1339
  */
2469
- buildSellTransaction(params: BuildOndoSellParams): Promise<OndoSellTransaction>;
2470
- }
2471
- declare class OndoNamespaceImpl implements OndoNamespace {
2472
- private readonly ctx;
2473
- protected readonly log: InternalLogger;
2474
- constructor(ctx: UniversalNamespaceContext);
2475
- getTradingStatus(params: GetOndoTradingStatusParams): Promise<OndoTradingStatus>;
2476
- getQuote(params: GetOndoQuoteParams): Promise<OndoQuote>;
2477
- buildBuyTransaction(params: BuildOndoBuyParams): Promise<OndoBuyTransaction>;
2478
- buildSellTransaction(params: BuildOndoSellParams): Promise<OndoSellTransaction>;
2479
- }
2480
-
1340
+ side: OrderBookSide;
1341
+ };
1342
+ /** How long Ondo should hold the price. Omit to take Ondo's own default. */
1343
+ type OndoQuoteDuration = 'short' | 'long';
2481
1344
  /**
2482
- * Raw JSON models for the Superstate swap endpoints that are still backend
2483
- * calls.
2484
- *
2485
- * The contract *reads* no longer have DTOs: the SDK calls `status()`,
2486
- * `preview()`, `authorized()` and `outputToken()` on-chain through
2487
- * `@/universal/api/rpc`, so their wire shape is the ABI rather than JSON.
2488
- * Allow-listing a wallet stays here because it is a backend write, not a read.
1345
+ * A quote is sized by the quantity or by the dollar amount. The endpoint takes
1346
+ * exactly one; `getOndoQuote` sends whichever is present.
2489
1347
  */
2490
- type AllowWalletResponseDto = {
2491
- action: 'broadcast_transaction';
2492
- to: string;
2493
- data: string;
1348
+ type OndoQuoteSize = {
1349
+ tokenAmount: BlockchainAmount;
2494
1350
  } | {
2495
- action: 'none';
2496
- already_allowed: boolean;
1351
+ notionalValue: DecimalString;
2497
1352
  };
2498
-
1353
+ type GetOndoQuoteParams = {
1354
+ symbol: AssetSymbol;
1355
+ side: OrderBookSide;
1356
+ duration?: OndoQuoteDuration;
1357
+ } & OndoQuoteSize;
2499
1358
  /**
2500
- * A read-only quote for a swap: how much goes in, the protocol fee, and how
2501
- * much would come out. All amounts are raw on-chain integers (uint256).
2502
- */
2503
- type SwapPreview = {
1359
+ * What both builders take, before the one field whose meaning forks.
1360
+ *
1361
+ * There is no funding token and no asset here - frontline resolves both from
1362
+ * the offer, because Ninshubur signs a request bound to them and a caller that
1363
+ * could name them could have CoinList sign for a contract of its own. Which
1364
+ * endpoint was called is what says which of the two `amount` counts.
1365
+ *
1366
+ * `chain` is required although the read params refuse it, because these name a
1367
+ * real contract on a real chain rather than asking Ondo for a price.
1368
+ */
1369
+ type BuildOndoSwapParamsCore = {
1370
+ symbol: AssetSymbol;
1371
+ chain: EthereumChain;
1372
+ /**
1373
+ * The wallet that will *send* the transaction, and that receives the other
1374
+ * token. The calldata is signed over it, so a transaction built for one
1375
+ * wallet and broadcast by another reverts.
1376
+ */
1377
+ walletAddress: EvmWalletAddress;
1378
+ };
1379
+ /**
1380
+ * What it takes to turn an indicative price into signed, fillable calldata for
1381
+ * a purchase: `POST /v1/ondo/swap/buy`.
1382
+ *
1383
+ * Separate from {@link BuildOndoSellParams} although the fields match today,
1384
+ * because `amount` is denominated in a different token on each - which is
1385
+ * frontline's own reason for splitting the endpoint rather than taking a
1386
+ * `side`. Sharing one type would re-merge the distinction the split exists to
1387
+ * make, and the two will diverge the day either side gains a knob.
1388
+ */
1389
+ type BuildOndoBuyParams = BuildOndoSwapParamsCore & {
1390
+ /**
1391
+ * The **gross** deposit, in the base units of the funding token - the coin
1392
+ * the user chose and approved.
1393
+ *
1394
+ * Sized by `amount` alone, with no `notionalValue` alternative: the calldata
1395
+ * authorises a specific ERC-20 pull, so the number that ends up on chain has
1396
+ * to be the number the caller meant rather than one derived from a dollar
1397
+ * figure. CoinList's fee comes off it, and Ondo prices the remainder.
1398
+ *
1399
+ * Its `decimals` are also what the response's `spend_input_decimals` is
1400
+ * checked against: frontline resolves the funding token from the offer
1401
+ * rather than from this request, so the two are independent answers to the
1402
+ * same question and a disagreement means the wrong token was sized.
1403
+ */
1404
+ amount: BlockchainAmount;
1405
+ };
1406
+ /**
1407
+ * What it takes to turn an indicative price into signed, fillable calldata for
1408
+ * a sale: `POST /v1/ondo/swap/sell`.
1409
+ *
1410
+ * See {@link BuildOndoBuyParams} for why this is its own type.
1411
+ */
1412
+ type BuildOndoSellParams = BuildOndoSwapParamsCore & {
1413
+ /**
1414
+ * The quantity of the **asset** to sell, in the asset's own base units - not
1415
+ * in the funding token's, which is the same integer meaning something 1e12
1416
+ * different.
1417
+ *
1418
+ * This is the figure the wallet must have approved: a sale delivers the
1419
+ * asset, so the swap contract pulls it with `transferFrom` exactly as it
1420
+ * pulls the deposit on a purchase.
1421
+ *
1422
+ * Its `decimals` are checked against the response's `spend_input_decimals`,
1423
+ * which frontline reads on-chain from the asset on `chain`. The SDK's own
1424
+ * answer comes from the quote, which resolves the asset on Ethereum mainnet,
1425
+ * so on a testnet these are two independent resolutions of two different
1426
+ * contracts - and the check is what says so.
1427
+ */
1428
+ amount: BlockchainAmount;
1429
+ };
1430
+
1431
+ /**
1432
+ * Ondo swap reads, plus the write that turns one into a fillable transaction.
1433
+ *
1434
+ * The two reads are free to poll - neither spends an attestation, so a client
1435
+ * may call them while the user edits an order. The two builders are not:
1436
+ * budget one call per order placed, plus one per refresh the user asks for.
1437
+ *
1438
+ * **One builder per side, mirroring the endpoints.** A purchase and a sale
1439
+ * agree on how to broadcast and on nothing else: a purchase commits to an
1440
+ * exact quantity, a sale to a range with a floor beneath it, and `amount` is
1441
+ * the funding token on one and the asset on the other. A single method taking
1442
+ * a `side` would have to return a union the caller then re-narrows, having
1443
+ * already decided which trade it was placing.
1444
+ *
1445
+ * **No CoinList fee is applied to a read quote, and no read discloses one.**
1446
+ * Ondo prices exactly the amount passed. A CoinList approval is fee-inclusive,
1447
+ * so sizing a quote against a user's approval without subtracting the fee
1448
+ * first overstates what they receive. A built transaction carries the fee
1449
+ * explicitly, and it is zero until one lands (ENG-1718).
1450
+ *
1451
+ * Every method requires `AuthState === 'logged-in'` and throws
1452
+ * {@link NotAuthenticatedError} otherwise.
1453
+ */
1454
+ interface OndoNamespace {
1455
+ getTradingStatus(params: GetOndoTradingStatusParams): Promise<OndoTradingStatus>;
1456
+ /**
1457
+ * An indicative price for `params`' size, from Ondo's live market.
1458
+ *
1459
+ * Ondo refuses to quote while the market is closed or paused, or the asset
1460
+ * is paused or closed for the session - including for about a minute at
1461
+ * every US session change, when `getTradingStatus` may still have reported
1462
+ * the asset tradable. The request then rejects with a 409 carrying
1463
+ * `code: "not_tradable"`; tell it apart from a failure with
1464
+ * {@link isOndoNotTradableError}. Retrying before the market opens fails the
1465
+ * same way.
1466
+ */
1467
+ getQuote(params: GetOndoQuoteParams): Promise<OndoQuote>;
1468
+ /**
1469
+ * Builds a purchase for a specific wallet and deposit: spends an attestation
1470
+ * and returns calldata to broadcast, valid until
1471
+ * {@link OndoBuyTransaction.expiresAt}.
1472
+ *
1473
+ * The wallet must have approved the swap contract to spend `amount` of the
1474
+ * offer's **funding token** first. Frontline reads that allowance before
1475
+ * asking Ninshubur for anything, and rejects a short one with a 422 carrying
1476
+ * `code: "insufficient_allowance"`. See `prepareBuy` on the client
1477
+ * namespace, which approves and then builds, in that order.
1478
+ *
1479
+ * The tokens themselves are the offer's rather than the caller's to name.
1480
+ */
1481
+ buildBuyTransaction(params: BuildOndoBuyParams): Promise<OndoBuyTransaction>;
1482
+ /**
1483
+ * Builds a sale for a specific wallet and quantity: spends an attestation
1484
+ * and returns calldata to broadcast, valid until
1485
+ * {@link OndoSellTransaction.expiresAt}.
1486
+ *
1487
+ * The approval this one needs is on the **asset**, not on a stablecoin - a
1488
+ * sale delivers the asset, so the swap contract pulls it with `transferFrom`
1489
+ * exactly as it pulls the deposit on a purchase. Same 422 when it is short,
1490
+ * with a message naming that address. See `prepareSell` on the client
1491
+ * namespace.
1492
+ *
1493
+ * Returns a range rather than a quantity: Ondo settles through USDon before
1494
+ * converting to the settlement token, so disclose
1495
+ * {@link OndoSellTransaction.minimum} and not only `expected`.
1496
+ */
1497
+ buildSellTransaction(params: BuildOndoSellParams): Promise<OndoSellTransaction>;
1498
+ }
1499
+
1500
+ /**
1501
+ * A read-only quote for a swap as domain amounts: how much goes in, the
1502
+ * protocol fee, and how much would come out. Derived from the raw
1503
+ * {@link SwapPreview} the swap contract returns.
1504
+ */
1505
+ type SwapQuote = {
1506
+ inputTokenAmount: BlockchainAmount;
1507
+ fee: BlockchainAmount;
1508
+ outputTokenAmount: BlockchainAmount;
1509
+ };
1510
+
1511
+ /** Parameters shared by contract reads scoped to a chain. */
1512
+ type SwapContractRef = {
1513
+ contractAddress: EvmContractAddress;
1514
+ chain: EthereumChain;
1515
+ };
1516
+ type GetSwapAuthorizationParams = SwapContractRef & {
1517
+ walletAddress: EvmWalletAddress;
1518
+ };
1519
+ type GetSwapPreviewParams = SwapContractRef & {
1520
+ inputToken: EvmContractAddress;
1521
+ amount: bigint;
1522
+ };
1523
+ type GetSwapQuoteParams = SwapContractRef & {
1524
+ inputToken: EvmContractAddress;
1525
+ /** The gross amount to swap, in the input token. */
1526
+ amount: BlockchainAmount;
1527
+ /**
1528
+ * Decimals of the contract's output token, from `getOutputToken`. A
1529
+ * parameter rather than read per call because a quote is polled and the
1530
+ * output token never changes.
1531
+ */
1532
+ outputTokenDecimals: AssetDecimals;
1533
+ };
1534
+ type AllowWalletParams = {
1535
+ offerId: OfferId;
1536
+ walletAddress: EvmWalletAddress;
1537
+ chain: EthereumChain;
1538
+ signature: string;
1539
+ };
1540
+
1541
+ /**
1542
+ * A read-only quote for a swap: how much goes in, the protocol fee, and how
1543
+ * much would come out. All amounts are raw on-chain integers (uint256).
1544
+ */
1545
+ type SwapPreview = {
2504
1546
  inputAmount: Uint256;
2505
1547
  fee: Uint256;
2506
1548
  outputAmount: Uint256;
2507
1549
  };
2508
- declare const SwapPreview: {
2509
- /** Maps the contract's `Preview { input, fee, output }` struct. */
2510
- fromContract: (preview: {
2511
- input: bigint;
2512
- fee: bigint;
2513
- output: bigint;
2514
- }) => SwapPreview;
2515
- };
2516
1550
  /**
2517
1551
  * The bitmask of operations a swap contract has paused, as `Operable.paused`
2518
1552
  * reports it. Each bit is one operation level; swapping is
@@ -2542,24 +1576,6 @@ type SwapStatus = {
2542
1576
  } | {
2543
1577
  state: 'stopped';
2544
1578
  };
2545
- declare const SwapStatus: {
2546
- /**
2547
- * Maps the contract's `Status { uint32 flags; State state }` struct, where
2548
- * `State` is `0 = Active | 1 = Paused | 2 = Stopped`.
2549
- *
2550
- * `flags` is carried onto the `paused` arm only. On the other two the
2551
- * contract sets it to a self-identifying marker rather than to information -
2552
- * so repeating it would invite a caller to read meaning into a constant.
2553
- *
2554
- * @throws ValidationError on a `state` outside the enum. A contract that has
2555
- * grown a fourth state is one the SDK does not model, and guessing `active`
2556
- * would let a halted swap read as a live one.
2557
- */
2558
- fromContract: (status: {
2559
- flags: number;
2560
- state: number;
2561
- }) => SwapStatus;
2562
- };
2563
1579
  /**
2564
1580
  * The backend's response to an allow-wallet request. Either the caller must
2565
1581
  * broadcast an on-chain transaction to complete allow-listing, or nothing is
@@ -2573,28 +1589,6 @@ type AllowWalletResponse = {
2573
1589
  action: 'none';
2574
1590
  alreadyAllowed: boolean;
2575
1591
  };
2576
- declare const AllowWalletResponse: {
2577
- fromDto: (dto: AllowWalletResponseDto) => AllowWalletResponse;
2578
- };
2579
-
2580
- /** Parameters shared by contract reads scoped to a chain. */
2581
- type SwapContractRef = {
2582
- contractAddress: EvmContractAddress;
2583
- chain: EthereumChain;
2584
- };
2585
- type GetSwapAuthorizationParams = SwapContractRef & {
2586
- walletAddress: EvmWalletAddress;
2587
- };
2588
- type GetSwapPreviewParams = SwapContractRef & {
2589
- inputToken: EvmContractAddress;
2590
- amount: bigint;
2591
- };
2592
- type AllowWalletParams = {
2593
- offerId: OfferId;
2594
- walletAddress: EvmWalletAddress;
2595
- chain: EthereumChain;
2596
- signature: string;
2597
- };
2598
1592
 
2599
1593
  /**
2600
1594
  * Read/write operations for the on-chain swap flow: quoting a swap, inspecting
@@ -2622,6 +1616,12 @@ interface SuperstateSwapNamespace {
2622
1616
  * {@link getStatus} first if you would rather not provoke one.
2623
1617
  */
2624
1618
  getPreview(params: GetSwapPreviewParams): Promise<SwapPreview>;
1619
+ /**
1620
+ * {@link getPreview} as domain amounts: what goes in, the fee, and what
1621
+ * would come out. This is the quote `ClientSuperstateSwapNamespace.execute`
1622
+ * takes, and it reverts on the same conditions.
1623
+ */
1624
+ getQuote(params: GetSwapQuoteParams): Promise<SwapQuote>;
2625
1625
  /**
2626
1626
  * Reads the current on-chain state of a swap contract. See
2627
1627
  * {@link isSwapAvailable} before concluding that a `paused` contract cannot
@@ -2641,113 +1641,190 @@ interface SuperstateSwapNamespace {
2641
1641
  */
2642
1642
  allowWallet(params: AllowWalletParams): Promise<AllowWalletResponse>;
2643
1643
  }
2644
- declare class SuperstateSwapNamespaceImpl implements SuperstateSwapNamespace {
2645
- private readonly ctx;
2646
- protected readonly log: InternalLogger;
2647
- constructor(ctx: UniversalNamespaceContext);
2648
- getAuthorization(params: GetSwapAuthorizationParams): Promise<boolean>;
2649
- getPreview(params: GetSwapPreviewParams): Promise<SwapPreview>;
2650
- getStatus(params: SwapContractRef): Promise<SwapStatus>;
2651
- getOutputToken(params: SwapContractRef): Promise<Erc20Asset>;
2652
- allowWallet(params: AllowWalletParams): Promise<AllowWalletResponse>;
2653
- }
2654
1644
 
2655
- /** Request body for `POST /v1/offers/:offer_id/addresses`. */
2656
- type CreateOfferOptionAddressDto = {
2657
- offer_option_id: string;
2658
- wallet_address: string;
2659
- chain: string;
2660
- signature: string;
2661
- };
2662
- /** Binding object returned by the `/v1/offers/:offer_id/addresses` resource. */
2663
- type OfferOptionAddressDto = {
2664
- id: string;
2665
- offer_option_id: string;
2666
- address: string;
2667
- protocol: WalletProtocol;
2668
- created_at: string;
1645
+ /** Document types that can be signed via {@link CoinListClient.submitDocument}. */
1646
+ type DocumentType = 'tax_certification';
1647
+ /** Signing-state machine status for a document submission. */
1648
+ type DocumentSubmissionStatus = 'INITIALIZED' | 'SENT' | 'VIEWED' | 'COMPLETED' | 'DECLINED' | 'EXPIRED';
1649
+ /** The tax form derived from the entity's kind (individual vs company/trust). */
1650
+ type DocumentFormType = 'w8_ben' | 'w8_ben_e';
1651
+ /** Result of starting (or resuming) a document signing submission. */
1652
+ type DocumentSubmission = {
1653
+ status: DocumentSubmissionStatus;
1654
+ formType: DocumentFormType;
2669
1655
  };
2670
1656
 
2671
- /** Unique identifier for a proven wallet binding on an offer option. */
2672
- type OfferOptionAddressId = Newtype<string, 'OfferOptionAddressId'>;
2673
- /** Casts a string into a typed {@link OfferOptionAddressId}. */
2674
- declare const OfferOptionAddressId: (value: string) => OfferOptionAddressId;
2675
1657
  /**
2676
- * A user's external wallet, proven via a wallet-ownership challenge and bound
2677
- * to an offer option. Returned by the `/v1/offers/:offer_id/addresses` resource.
1658
+ * Sumsub verification level name. Determines which screens the Sumsub WebSDK
1659
+ * shows (levels are configured in the Sumsub dashboard). The backend
1660
+ * prescribes the level (and whether the applicant must be reset first) in the
1661
+ * requirement statuses response — clients never compute levels themselves.
2678
1662
  */
2679
- type OfferOptionAddress = {
2680
- /** Unique binding id. */
2681
- id: OfferOptionAddressId;
2682
- /** Offer option the wallet is bound to. */
2683
- offerOptionId: OfferOptionId;
2684
- /** The connected external wallet address. */
2685
- address: EvmWalletAddress;
2686
- /**
2687
- * Protocol the binding is scoped to. An EVM address binds once per option
2688
- * regardless of which EVM chain proved ownership.
2689
- */
2690
- protocol: WalletProtocol;
2691
- /** When the binding was created. */
2692
- createdAt: Date;
2693
- };
2694
- declare const OfferOptionAddress: {
2695
- /** Maps the API DTO into the SDK offer-option-address domain model. */
2696
- fromDto: (dto: OfferOptionAddressDto) => OfferOptionAddress;
2697
- };
2698
- /** Parameters required to connect a proven external wallet to an offer option. */
2699
- type ConnectExternalWalletParams = {
2700
- /** Offer the option belongs to. */
2701
- offerId: OfferId;
2702
- /** Offer option to bind the wallet to. */
2703
- offerOptionId: OfferOptionId;
2704
- /** External wallet address that was proven. */
2705
- walletAddress: EvmWalletAddress;
2706
- /** Chain the ownership was proven on. */
2707
- chain: EthereumChain;
2708
- /** Signature of the wallet-ownership challenge message. */
2709
- signature: Hex;
2710
- };
2711
- declare const ConnectExternalWalletParams: {
2712
- /** Maps connect-wallet params into the API DTO payload. */
2713
- toDto: (params: ConnectExternalWalletParams) => CreateOfferOptionAddressDto;
1663
+ type KycLevelName = string;
1664
+ /** Short-lived Sumsub WebSDK access token scoped to the current user. */
1665
+ type KycToken = {
1666
+ token: string;
2714
1667
  };
2715
1668
 
2716
- /** Challenge kinds accepted by `POST /v1/wallet-ownership`. */
2717
- type WalletOwnershipChallengeTypeDto = 'plain' | 'siwe';
2718
- /**
2719
- * Request body for `POST /v1/wallet-ownership`. `challenge_type` defaults to
2720
- * `plain` on the backend; the SIWE fields (`domain`, `uri`, `statement`) are
2721
- * required only when `challenge_type` is `siwe` and must be absent otherwise.
2722
- */
2723
- type CreateWalletOwnershipChallengeDto = {
2724
- wallet_address: string;
2725
- chain: string;
2726
- challenge_type?: WalletOwnershipChallengeTypeDto;
2727
- domain?: string;
2728
- uri?: string;
2729
- statement?: string;
2730
- };
2731
- /** Response body for `POST /v1/wallet-ownership`. */
2732
- type WalletOwnershipChallengeDto = {
2733
- message: string;
2734
- expires_at: string;
1669
+ /** Whether the PII belongs to an individual or a company/trust entity. */
1670
+ type PiiKind = 'person' | 'company';
1671
+ /** ISO 3166-1 alpha-2 country code (e.g. `'US'`). */
1672
+ type Iso2CountryCode = Newtype<string, 'Iso2CountryCode'>;
1673
+ declare const Iso2CountryCode: (value: string) => Iso2CountryCode;
1674
+ /** Jurisdiction derived from the entity's address country. */
1675
+ type PiiJurisdiction = {
1676
+ iso2: Iso2CountryCode;
1677
+ name: string | null;
1678
+ };
1679
+ /** Permanent address on file for the entity. */
1680
+ type PiiAddress = {
1681
+ street: string | null;
1682
+ city: string | null;
1683
+ state: string | null;
1684
+ postalCode: string | null;
1685
+ country: string | null;
1686
+ };
1687
+ /**
1688
+ * The current user's PII, used to pre-fill tax forms such as the W-8BEN.
1689
+ * Fields the entity hasn't provided are `null`.
1690
+ */
1691
+ type Pii = {
1692
+ kind: PiiKind;
1693
+ fullLegalName: string | null;
1694
+ dateOfBirth: string | null;
1695
+ jurisdiction: PiiJurisdiction | null;
1696
+ taxId: string | null;
1697
+ permanentAddress: PiiAddress;
2735
1698
  };
2736
1699
 
2737
- /** Fields common to every wallet-ownership challenge request. */
2738
- type WalletOwnershipChallengeParamsBase = {
2739
- /** Wallet address to prove ownership of. */
2740
- walletAddress: EvmWalletAddress;
2741
- /** Chain the wallet belongs to. */
2742
- chain: EthereumChain;
1700
+ type RequirementId = Newtype<string, 'RequirementId'>;
1701
+ declare const RequirementId: (value: string) => RequirementId;
1702
+ type RequirementType = 'kyc_approved' | 'external_wallet' | 'whitelisted_wallet' | 'jurisdiction' | 'accreditation' | 'document';
1703
+ type RequirementStatusValue = 'not_started' | 'in_progress' | 'action_needed' | 'completed' | 'rejected';
1704
+ type RequirementActionNeededReason = 'kyc_not_verified' | 'update_pii_data';
1705
+ type Requirement = {
1706
+ id: RequirementId;
1707
+ type: RequirementType;
1708
+ details: Record<string, unknown> | null;
1709
+ };
1710
+ type RequirementStatusInfo = {
1711
+ id: RequirementId;
1712
+ status: RequirementStatusValue;
1713
+ /** Why the requirement needs action (KYC-backed requirements only). */
1714
+ action: RequirementActionNeededReason | null;
1715
+ /**
1716
+ * The Sumsub verification level that resolves this requirement, prescribed
1717
+ * by the backend. Present exactly when an inline Sumsub flow can be started.
1718
+ */
1719
+ kycLevel?: KycLevelName;
1720
+ /**
1721
+ * Whether the Sumsub applicant must be reset before starting the flow
1722
+ * (redoing an already-approved level, e.g. to update stale PII). Forward to
1723
+ * the kyc-token request as-is.
1724
+ */
1725
+ kycReset?: boolean;
1726
+ };
1727
+
1728
+ type CreateKycTokenParams = {
1729
+ /**
1730
+ * Sumsub verification level to run. Defaults to the backend's standard
1731
+ * level.
1732
+ */
1733
+ levelName?: KycLevelName;
1734
+ /**
1735
+ * Resets the Sumsub applicant first, so an already-approved level can be
1736
+ * executed again (e.g. to update stale PII). Pass the `kycReset` value from
1737
+ * the requirement status, and never on mid-flow token refreshes.
1738
+ */
1739
+ reset?: boolean;
1740
+ };
1741
+ type SubmitDocumentParams = {
1742
+ /** Currently only `tax_certification` (W-8BEN / W-8BEN-E). */
1743
+ documentType: DocumentType;
1744
+ /**
1745
+ * Signing-form values keyed by the document's DocuSeal field names,
1746
+ * forwarded verbatim to pre-fill the document.
1747
+ */
1748
+ fields: Record<string, string>;
2743
1749
  };
2744
1750
  /**
2745
- * How the ownership challenge is framed: a plain message or a Sign-In With
2746
- * Ethereum challenge. Extracted as its own type so SDK consumers can pass it as
2747
- * a standalone param without reaching into the {@link CreateWalletOwnershipChallengeParams}
2748
- * union.
1751
+ * Everything a user must satisfy before they can participate in an offer:
1752
+ * reading the checklist and its live statuses, and the operations that satisfy
1753
+ * individual requirements — identity verification (KYC) and tax-document
1754
+ * signing.
1755
+ *
1756
+ * Wallet requirements (`external_wallet`, `whitelisted_wallet`) are satisfied
1757
+ * through `WalletsNamespace` instead, since wallet proofs are also used
1758
+ * outside the requirements flow.
1759
+ *
1760
+ * Every method requires an authenticated user and throws
1761
+ * {@link NotAuthenticatedError} otherwise.
2749
1762
  */
2750
- type WalletChallengeType = CreateWalletOwnershipChallengeParams['challengeType'];
1763
+ interface RequirementsNamespace {
1764
+ /**
1765
+ * Fetches the requirements for every option of an offer, grouped by option
1766
+ * id. This is the definition of the checklist; {@link statuses} tells you
1767
+ * where the user stands against it.
1768
+ */
1769
+ forOffer(offerId: OfferId): Promise<Record<OfferOptionId, Requirement[]>>;
1770
+ /**
1771
+ * Fetches the current user's status for each requirement of an offer.
1772
+ */
1773
+ statuses(offerId: OfferId): Promise<RequirementStatusInfo[]>;
1774
+ /**
1775
+ * Creates a short-lived Sumsub WebSDK access token so an identity
1776
+ * verification (KYC) flow can be started, e.g. by the `IdentityVerification`
1777
+ * component.
1778
+ */
1779
+ createKycToken(params?: CreateKycTokenParams): Promise<KycToken>;
1780
+ /**
1781
+ * Fetches the current user's PII, used to pre-fill tax forms such as the
1782
+ * W-8BEN. Fields the entity hasn't provided are `null`.
1783
+ */
1784
+ getPii(): Promise<Pii>;
1785
+ /**
1786
+ * Starts (or resumes) a document signing submission.
1787
+ */
1788
+ submitDocument(params: SubmitDocumentParams): Promise<DocumentSubmission>;
1789
+ }
1790
+
1791
+ /** Unique identifier for a proven wallet binding on an offer option. */
1792
+ type OfferOptionAddressId = Newtype<string, 'OfferOptionAddressId'>;
1793
+ /** Casts a string into a typed {@link OfferOptionAddressId}. */
1794
+ declare const OfferOptionAddressId: (value: string) => OfferOptionAddressId;
1795
+ /**
1796
+ * A user's external wallet, proven via a wallet-ownership challenge and bound
1797
+ * to an offer option. Returned by the `/v1/offers/:offer_id/addresses` resource.
1798
+ */
1799
+ type OfferOptionAddress = {
1800
+ /** Unique binding id. */
1801
+ id: OfferOptionAddressId;
1802
+ /** Offer option the wallet is bound to. */
1803
+ offerOptionId: OfferOptionId;
1804
+ /** The connected external wallet address. */
1805
+ address: EvmWalletAddress;
1806
+ /**
1807
+ * Protocol the binding is scoped to. An EVM address binds once per option
1808
+ * regardless of which EVM chain proved ownership.
1809
+ */
1810
+ protocol: WalletProtocol;
1811
+ /** When the binding was created. */
1812
+ createdAt: Date;
1813
+ };
1814
+ /** Parameters required to connect a proven external wallet to an offer option. */
1815
+ type ConnectExternalWalletParams = {
1816
+ /** Offer the option belongs to. */
1817
+ offerId: OfferId;
1818
+ /** Offer option to bind the wallet to. */
1819
+ offerOptionId: OfferOptionId;
1820
+ /** External wallet address that was proven. */
1821
+ walletAddress: EvmWalletAddress;
1822
+ /** Chain the ownership was proven on. */
1823
+ chain: EthereumChain;
1824
+ /** Signature of the wallet-ownership challenge message. */
1825
+ signature: Hex;
1826
+ };
1827
+
2751
1828
  /**
2752
1829
  * A single-use ownership challenge returned by `POST /v1/wallet-ownership`.
2753
1830
  * The consumer signs {@link message} with their wallet, then submits the
@@ -2759,9 +1836,12 @@ type WalletOwnershipChallenge = {
2759
1836
  /** When the challenge expires and can no longer be consumed. */
2760
1837
  expiresAt: Date;
2761
1838
  };
2762
- declare const WalletOwnershipChallenge: {
2763
- /** Maps the API DTO into the SDK wallet-ownership-challenge domain model. */
2764
- fromDto: (dto: WalletOwnershipChallengeDto) => WalletOwnershipChallenge;
1839
+ /** Fields common to every wallet-ownership challenge request. */
1840
+ type WalletOwnershipChallengeParamsBase = {
1841
+ /** Wallet address to prove ownership of. */
1842
+ walletAddress: EvmWalletAddress;
1843
+ /** Chain the wallet belongs to. */
1844
+ chain: EthereumChain;
2765
1845
  };
2766
1846
  /**
2767
1847
  * Parameters for requesting a wallet-ownership challenge. Modeled as a
@@ -2781,14 +1861,13 @@ type CreateWalletOwnershipChallengeParams = (WalletOwnershipChallengeParamsBase
2781
1861
  /** Human-readable statement shown in the signing prompt. */
2782
1862
  statement: string;
2783
1863
  });
2784
- declare const CreateWalletOwnershipChallengeParams: {
2785
- /**
2786
- * Maps challenge-request params into the API DTO payload. The discriminated
2787
- * union guarantees SIWE fields are present exactly when `challengeType` is
2788
- * `siwe`, so the mapping narrows on the discriminant.
2789
- */
2790
- toDto: (params: CreateWalletOwnershipChallengeParams) => CreateWalletOwnershipChallengeDto;
2791
- };
1864
+ /**
1865
+ * How the ownership challenge is framed: a plain message or a Sign-In With
1866
+ * Ethereum challenge. Extracted as its own type so SDK consumers can pass it as
1867
+ * a standalone param without reaching into the {@link CreateWalletOwnershipChallengeParams}
1868
+ * union.
1869
+ */
1870
+ type WalletChallengeType = CreateWalletOwnershipChallengeParams['challengeType'];
2792
1871
 
2793
1872
  type ListOptionAddressesParams = {
2794
1873
  offerId: OfferId;
@@ -2835,467 +1914,798 @@ interface WalletsNamespace {
2835
1914
  */
2836
1915
  remove(params: RemoveOptionAddressParams): Promise<OfferOptionAddress>;
2837
1916
  }
2838
- declare class WalletsNamespaceImpl implements WalletsNamespace {
2839
- private readonly ctx;
2840
- private readonly log;
2841
- constructor(ctx: UniversalNamespaceContext);
2842
- createOwnershipChallenge(params: CreateWalletOwnershipChallengeParams): Promise<WalletOwnershipChallenge>;
2843
- connectExternal(params: ConnectExternalWalletParams): Promise<OfferOptionAddress>;
2844
- list(params: ListOptionAddressesParams): Promise<OfferOptionAddress[]>;
2845
- remove(params: RemoveOptionAddressParams): Promise<OfferOptionAddress>;
2846
- }
2847
-
2848
- type DocumentSubmissionStatusDto = 'INITIALIZED' | 'SENT' | 'VIEWED' | 'COMPLETED' | 'DECLINED' | 'EXPIRED';
2849
- type DocumentFormTypeDto = 'w8_ben' | 'w8_ben_e';
2850
- type DocumentSubmissionDto = {
2851
- object: 'document_submission';
2852
- status: DocumentSubmissionStatusDto;
2853
- form_type: DocumentFormTypeDto;
2854
- };
2855
-
2856
- /** Document types that can be signed via {@link CoinListClient.submitDocument}. */
2857
- type DocumentType = 'tax_certification';
2858
- /** Signing-state machine status for a document submission. */
2859
- type DocumentSubmissionStatus = DocumentSubmissionStatusDto;
2860
- /** The tax form derived from the entity's kind (individual vs company/trust). */
2861
- type DocumentFormType = DocumentFormTypeDto;
2862
- /** Result of starting (or resuming) a document signing submission. */
2863
- type DocumentSubmission = {
2864
- status: DocumentSubmissionStatus;
2865
- formType: DocumentFormType;
2866
- };
2867
- declare const DocumentSubmission: {
2868
- fromDto: (dto: DocumentSubmissionDto) => DocumentSubmission;
2869
- };
2870
-
2871
- type KycTokenDto = {
2872
- object: 'kyc_token';
2873
- token: string;
2874
- };
2875
1917
 
2876
1918
  /**
2877
- * Sumsub verification level name. Determines which screens the Sumsub WebSDK
2878
- * shows (levels are configured in the Sumsub dashboard). The backend
2879
- * prescribes the level (and whether the applicant must be reset first) in the
2880
- * requirement statuses response — clients never compute levels themselves.
1919
+ * Reads over CoinList's offers: the catalogue a user can browse, and the full
1920
+ * detail of a single offer.
1921
+ *
1922
+ * Every method requires an authenticated user and throws
1923
+ * {@link NotAuthenticatedError} otherwise. On the server, the same reads are
1924
+ * additionally available with an app-level token — see
1925
+ * `ServerOffersNamespace`.
2881
1926
  */
2882
- type KycLevelName = string;
2883
- /** Short-lived Sumsub WebSDK access token scoped to the current user. */
2884
- type KycToken = {
2885
- token: string;
2886
- };
2887
- declare const KycToken: {
2888
- fromDto: (dto: KycTokenDto) => KycToken;
2889
- };
1927
+ interface OffersNamespace {
1928
+ /**
1929
+ * Fetches every offer, iterating through all pages. Prefer {@link listPage}
1930
+ * when you render a paginated list yourself.
1931
+ */
1932
+ list(): Promise<Offer[]>;
1933
+ /**
1934
+ * Fetches a single page of offers. Pass the previous response's
1935
+ * `startingAfter` as `after` to advance.
1936
+ */
1937
+ listPage(params: PaginationParams): Promise<PaginatedResponse<Offer>>;
1938
+ /**
1939
+ * Fetches the full detail of one offer, including its options. Note this
1940
+ * returns {@link OfferDetail} — a richer model than the {@link Offer}
1941
+ * summaries {@link list} returns.
1942
+ */
1943
+ get(id: OfferId): Promise<OfferDetail>;
1944
+ }
2890
1945
 
2891
- type PiiKindDto = 'person' | 'company';
2892
- type PiiJurisdictionDto = {
2893
- iso_2: string;
2894
- name: string | null;
2895
- };
2896
- type PiiAddressDto = {
2897
- street: string | null;
2898
- city: string | null;
2899
- state: string | null;
2900
- postal_code: string | null;
2901
- country: string | null;
1946
+ type GetTokenAllowanceParams = {
1947
+ tokenAddress: EvmContractAddress;
1948
+ owner: EvmWalletAddress;
1949
+ spender: EvmContractAddress;
1950
+ chain: EthereumChain;
2902
1951
  };
2903
- type PiiDto = {
2904
- object: 'user_pii';
2905
- kind: PiiKindDto;
2906
- full_legal_name: string | null;
2907
- date_of_birth: string | null;
2908
- jurisdiction: PiiJurisdictionDto | null;
2909
- tax_id: string | null;
2910
- permanent_address: PiiAddressDto;
1952
+ type GetTokenBalanceParams = {
1953
+ tokenAddress: EvmContractAddress;
1954
+ owner: EvmWalletAddress;
1955
+ chain: EthereumChain;
2911
1956
  };
2912
1957
 
2913
- /** Whether the PII belongs to an individual or a company/trust entity. */
2914
- type PiiKind = PiiKindDto;
2915
- /** ISO 3166-1 alpha-2 country code (e.g. `'US'`). */
2916
- type Iso2CountryCode = Newtype<string, 'Iso2CountryCode'>;
2917
- declare const Iso2CountryCode: (value: string) => Iso2CountryCode;
2918
- /** Jurisdiction derived from the entity's address country. */
2919
- type PiiJurisdiction = {
2920
- iso2: Iso2CountryCode;
2921
- name: string | null;
2922
- };
2923
- declare const PiiJurisdiction: {
2924
- fromDto: (dto: PiiJurisdictionDto) => PiiJurisdiction;
2925
- };
2926
- /** Permanent address on file for the entity. */
2927
- type PiiAddress = {
2928
- street: string | null;
2929
- city: string | null;
2930
- state: string | null;
2931
- postalCode: string | null;
2932
- country: string | null;
2933
- };
2934
- declare const PiiAddress: {
2935
- fromDto: (dto: PiiAddressDto) => PiiAddress;
2936
- };
2937
1958
  /**
2938
- * The current user's PII, used to pre-fill tax forms such as the W-8BEN.
2939
- * Fields the entity hasn't provided are `null`.
1959
+ * Generic ERC-20 reads shared across on-chain flows (swap, token sale): the
1960
+ * allowance an owner has granted a spender, and the raw token balance an owner
1961
+ * holds. These are plain token reads, not tied to any single product flow.
1962
+ *
1963
+ * Read from the chain rather than from a CoinList endpoint. By default that
1964
+ * goes through CoinList's authenticated JSON-RPC proxy, so a logged-out caller
1965
+ * gets the proxy's `401` - surfaced as a viem error wrapping the SDK's
1966
+ * `HttpError`, not as `NotAuthenticatedError`, since viem re-wraps whatever a
1967
+ * `Transport` throws. Point `Config.rpc` at your own node for this chain and no
1968
+ * CoinList session is involved at all. See {@link RpcConfig}.
2940
1969
  */
2941
- type Pii = {
2942
- kind: PiiKind;
2943
- fullLegalName: string | null;
2944
- dateOfBirth: string | null;
2945
- jurisdiction: PiiJurisdiction | null;
2946
- taxId: string | null;
2947
- permanentAddress: PiiAddress;
2948
- };
2949
- declare const Pii: {
2950
- fromDto: (dto: PiiDto) => Pii;
2951
- };
1970
+ interface Erc20Namespace {
1971
+ /**
1972
+ * Reads the ERC-20 allowance an `owner` has granted a `spender`, as a raw
1973
+ * on-chain integer.
1974
+ */
1975
+ getAllowance(params: GetTokenAllowanceParams): Promise<Uint256>;
1976
+ /**
1977
+ * Reads the raw ERC-20 balance an `owner` holds of a token.
1978
+ */
1979
+ getBalance(params: GetTokenBalanceParams): Promise<Uint256>;
1980
+ }
2952
1981
 
2953
- type RequirementTypeDto = 'kyc_approved' | 'external_wallet' | 'whitelisted_wallet' | 'jurisdiction' | 'accreditation' | 'document';
2954
- type RequirementDto = {
2955
- object: 'requirement';
2956
- id: string;
2957
- type: RequirementTypeDto;
2958
- details: Record<string, unknown> | null;
2959
- };
2960
- type RequirementStatusValueDto = 'not_started' | 'in_progress' | 'action_needed' | 'completed' | 'rejected';
2961
- type RequirementActionNeededReasonDto = 'kyc_not_verified' | 'update_pii_data';
2962
1982
  /**
2963
- * Object form of a requirement status, used when the status carries extra
2964
- * data: the action-needed reason and/or the Sumsub flow that resolves the
2965
- * requirement (kyc_level + kyc_reset, forwarded to the kyc-token endpoint).
1983
+ * Token display metadata — name, symbol, decimals, and logos — from
1984
+ * CoinList's public token registry, keyed by {@link TokenIdentifier} (the
1985
+ * same chain + address pairs `Offer.tokens` carries).
1986
+ *
1987
+ * Unlike the other namespaces, this one is public: no method requires an
1988
+ * authenticated user, and nothing here touches the CoinList API — reads go to
1989
+ * the registry's CDN.
2966
1990
  */
2967
- type RequirementStatusObjectDto = {
2968
- status: RequirementStatusValueDto;
2969
- action?: RequirementActionNeededReasonDto;
2970
- kyc_level?: string;
2971
- kyc_reset?: boolean;
2972
- };
2973
- type RequirementStatusesDto = {
2974
- object: 'requirement_statuses';
2975
- offer_id: string;
2976
- statuses: Record<string, RequirementStatusValueDto | RequirementStatusObjectDto>;
2977
- };
2978
-
2979
- type RequirementId = Newtype<string, 'RequirementId'>;
2980
- declare const RequirementId: (value: string) => RequirementId;
2981
- type RequirementType = RequirementTypeDto;
2982
- type RequirementStatusValue = RequirementStatusValueDto;
2983
- type RequirementActionNeededReason = RequirementActionNeededReasonDto;
2984
- type Requirement = {
2985
- id: RequirementId;
2986
- type: RequirementType;
2987
- details: Record<string, unknown> | null;
2988
- };
2989
- declare const Requirement: {
2990
- fromDto: (dto: RequirementDto) => Requirement;
2991
- };
2992
- type RequirementStatusInfo = {
2993
- id: RequirementId;
2994
- status: RequirementStatusValue;
2995
- /** Why the requirement needs action (KYC-backed requirements only). */
2996
- action: RequirementActionNeededReason | null;
1991
+ interface TokensNamespace {
2997
1992
  /**
2998
- * The Sumsub verification level that resolves this requirement, prescribed
2999
- * by the backend. Present exactly when an inline Sumsub flow can be started.
1993
+ * Fetches metadata for one token. Returns `null` when the registry does not
1994
+ * list the token — the registry is curated, so callers must fall back to
1995
+ * their own display defaults rather than treat this as an error.
3000
1996
  */
3001
- kycLevel?: KycLevelName;
1997
+ get(token: TokenIdentifier): Promise<TokenMetadata | null>;
3002
1998
  /**
3003
- * Whether the Sumsub applicant must be reset before starting the flow
3004
- * (redoing an already-approved level, e.g. to update stale PII). Forward to
3005
- * the kyc-token request as-is.
1999
+ * Fetches every token the registry lists, in one request: the complete
2000
+ * snapshot with no `chain`, or one chain's snapshot with it. Prefer this
2001
+ * over calling {@link get} in a loop when displaying a catalogue — two
2002
+ * hundred tokens across three chains is still a single download.
2003
+ *
2004
+ * The complete snapshot spans every chain the registry knows; tokens on
2005
+ * chains this SDK does not model (e.g. Solana) are left out of the result.
2006
+ *
2007
+ * Unlike {@link get}, a missing snapshot throws rather than returning `[]`:
2008
+ * the registry always publishes the complete snapshot and one per chain it
2009
+ * knows, so an absence is a deployment problem, not an empty catalogue.
3006
2010
  */
3007
- kycReset?: boolean;
3008
- };
3009
- declare const RequirementStatusInfo: {
3010
- fromStatusesDto: (dto: RequirementStatusesDto) => RequirementStatusInfo[];
3011
- };
2011
+ list(chain?: EthereumChain): Promise<TokenMetadata[]>;
2012
+ }
3012
2013
 
3013
- type CreateKycTokenParams = {
2014
+ /**
2015
+ * The observability seam a host implements to see inside the SDK.
2016
+ *
2017
+ * Pass an implementation as {@link Config.logger} and the SDK reports every
2018
+ * request it makes, every failure it classifies, and every state a hook
2019
+ * settles into. Leave it out and the SDK logs nothing at all - there is no
2020
+ * fallback to `console`, at any level, on any codepath.
2021
+ *
2022
+ * Four methods over a {@link LogEvent} lambda, plus {@link level}. An event is
2023
+ * a constant `msg`, the {@link LogScope} it came from, the accumulated
2024
+ * {@link LogBindings}, and a bag of `fields` - so a host forwards structured
2025
+ * records rather than parsing strings.
2026
+ *
2027
+ * ```ts
2028
+ * const logger: Logger = {
2029
+ * level: () => 'info',
2030
+ * debug: () => {},
2031
+ * info: (event) => sink.info(toRecord(event())),
2032
+ * warn: (event) => sink.warn(toRecord(event())),
2033
+ * error: (event) => sink.error(toRecord(event())),
2034
+ * };
2035
+ * ```
2036
+ *
2037
+ * {@link pinoClientLogger} and {@link pinoServerLogger} are ready-made
2038
+ * implementations over pino, and are what most hosts should reach for.
2039
+ *
2040
+ * ## Where to run it
2041
+ *
2042
+ * **Not in production, as the default answer.** Development, tests, staging,
2043
+ * and reproducing a reported bug are where this seam earns its keep. Left
2044
+ * undefined elsewhere it discloses nothing, and that holds no matter what a
2045
+ * later revision of the SDK puts on a line.
2046
+ *
2047
+ * Where you do run one in production, `'info'` and above is the only sane
2048
+ * choice, and the shipped implementations make the alternative unrepresentable
2049
+ * rather than merely discouraged: `isDev: false` narrows the level to
2050
+ * {@link ProductionLogLevel}, so `'debug'` does not compile. That narrowing is
2051
+ * a guard for the partner who ignored this paragraph, not an endorsement of
2052
+ * the practice.
2053
+ *
2054
+ * The distinction worth holding onto: `'info'` and above are **redacted by
2055
+ * construction** - see the next section for exactly what that covers, and the
2056
+ * type that enforces it. Redacted is a mechanism the SDK can hold itself to. A
2057
+ * blanket "safe in production" is a promise about your environment that the
2058
+ * SDK is in no position to make, and one that a single future field could
2059
+ * quietly break.
2060
+ *
2061
+ * `'debug'` is for reproducing a bug: a developer's machine, tests, and beta
2062
+ * or staging environments where the data flowing through the SDK is not real
2063
+ * customer data.
2064
+ *
2065
+ * ## What each level may disclose
2066
+ *
2067
+ * **`'debug'` is unredacted. Every other level is not.**
2068
+ *
2069
+ * At `'debug'` the SDK reports request and response bodies, request headers,
2070
+ * the full URL including its query string, and operation parameters
2071
+ * **verbatim** - bearer tokens, KYC answers, tax-document fields, wallet
2072
+ * signatures, the lot. There is no denylist and no redaction: see ADR-8 for
2073
+ * why a partial guarantee was rejected in favour of an honest absence of one.
2074
+ *
2075
+ * At `'info'`, `'warn'` and `'error'` the SDK emits only its own
2076
+ * classification of what happened and identifiers the server minted: a method
2077
+ * and a bare path, a status, a duration, a {@link RequestId}, a frontline
2078
+ * error code and event id, a {@link LogCause} arm. No request body, no
2079
+ * response body, no query string, no host-supplied parameters, and no message
2080
+ * text read off a thrown error the SDK did not author.
2081
+ *
2082
+ * The type carries that boundary rather than merely documenting it:
2083
+ * {@link SafeEvent} constrains every field value to a {@link LogValue} scalar,
2084
+ * so spreading a body, a DTO or an operation's params into an `info`, `warn`
2085
+ * or `error` line does not compile. Only {@link DebugEvent} accepts arbitrary
2086
+ * values.
2087
+ *
2088
+ * The one deliberate exception is an SDK-authored validation message, which
2089
+ * quotes the single scalar that failed to map - `Unsupported chain:
2090
+ * "solana_mainnet"`. It is a hand-written template naming one offending value,
2091
+ * not a payload, and it is the whole reason this seam exists.
2092
+ *
2093
+ * ## Your responsibility
2094
+ *
2095
+ * The SDK hands you events. What happens next is yours: **you own the final
2096
+ * filtering, redaction and retention** before anything is written to a file, a
2097
+ * console, or a third-party sink such as Sentry or Datadog. In particular, if
2098
+ * you implement {@link debug} and forward it anywhere durable, you have taken
2099
+ * on storing live credentials and personal data, deliberately. Implement
2100
+ * `debug` as a no-op - or return a level below it from {@link level} - in any
2101
+ * environment where that is not acceptable.
2102
+ *
2103
+ * **Every event is a lambda, and that is load bearing.** The SDK consults
2104
+ * {@link level} first and never invokes the lambda when the level forbids it,
2105
+ * so an expensive debug event - a serialized DTO, a loop over balances - costs
2106
+ * nothing in production where the level is `'error'` or `'none'`. Build the
2107
+ * event inside the lambda, never outside it.
2108
+ *
2109
+ * ## Every method must be total
2110
+ *
2111
+ * **None of the five methods may throw, and that includes {@link level}.** The
2112
+ * SDK calls them inline, on the codepath of the work it is reporting, and it
2113
+ * does not catch them: a `logger.error` that throws while reporting a failed
2114
+ * request turns that request's rejection into your logger's exception, and a
2115
+ * `level()` that throws breaks the call it was asked about even when nothing
2116
+ * else went wrong.
2117
+ *
2118
+ * That is deliberate. Swallowing would hide a logger that is broken in every
2119
+ * environment, and the SDK cannot tell a transport blip in your sink from a
2120
+ * bug in your implementation. So the contract is on this side of the seam:
2121
+ * catch inside your implementation, and never let an exception out. The
2122
+ * shipped pino implementations do exactly that.
2123
+ *
2124
+ * ```ts
2125
+ * error: (event) => {
2126
+ * try {
2127
+ * const e = event();
2128
+ * Sentry.captureMessage(e.msg, { extra: e });
2129
+ * } catch {
2130
+ * // never rethrow: the SDK is on the other side of this call
2131
+ * }
2132
+ * },
2133
+ * ```
2134
+ */
2135
+ interface Logger {
3014
2136
  /**
3015
- * Sumsub verification level to run. Defaults to the backend's standard
3016
- * level.
2137
+ * The most detailed level this logger wants. Consulted before **every** log
2138
+ * call, so keep it cheap and pure: return a captured constant or read a
2139
+ * plain field. Do not read an environment variable, hit storage, or call
2140
+ * out to a feature-flag service here.
2141
+ *
2142
+ * Returning `'none'` silences the SDK as completely as omitting the logger.
2143
+ *
2144
+ * **Must not throw.** It is consulted inside every SDK operation, so an
2145
+ * exception here fails the operation itself.
3017
2146
  */
3018
- levelName?: KycLevelName;
2147
+ level(): LogLevel;
3019
2148
  /**
3020
- * Resets the Sumsub applicant first, so an already-approved level can be
3021
- * executed again (e.g. to update stale PII). Pass the `kycReset` value from
3022
- * the requirement status, and never on mid-flow token refreshes.
2149
+ * Fine-grained detail: request and response bodies, full URLs, headers,
2150
+ * operation parameters, hook transitions.
2151
+ *
2152
+ * **Unredacted.** This is the only level that may carry credentials or
2153
+ * personal data, and it always may. Its event is a {@link DebugEvent}, whose
2154
+ * fields accept arbitrary values for exactly that reason. Do not forward it
2155
+ * to a durable or third-party sink outside development and beta.
2156
+ *
2157
+ * **Must not throw**, here and on every method below.
3023
2158
  */
3024
- reset?: boolean;
3025
- };
3026
- type SubmitDocumentParams = {
3027
- /** Currently only `tax_certification` (W-8BEN / W-8BEN-E). */
3028
- documentType: DocumentType;
2159
+ debug(event: () => DebugEvent): void;
2160
+ /** Notable, expected events: a request completing, a flow advancing. */
2161
+ info(event: () => SafeEvent): void;
3029
2162
  /**
3030
- * Signing-form values keyed by the document's DocuSeal field names,
3031
- * forwarded verbatim to pre-fill the document.
3032
- */
3033
- fields: Record<string, string>;
2163
+ * Recoverable trouble: a retried request, a renewed session, a poll tick
2164
+ * that failed while the last value still stands.
2165
+ *
2166
+ * Carries a {@link LogCause} when one was classified, the same as
2167
+ * {@link error} does. What separates the two levels is whether anything
2168
+ * actually broke, not how much is known about it.
2169
+ */
2170
+ warn(event: () => SafeEvent): void;
2171
+ /**
2172
+ * A failure. {@link SafeEvent.cause} carries what the SDK knows about it,
2173
+ * classified into {@link LogCause}, and is absent when the failure is not an
2174
+ * error the SDK caught - a flow reporting the step it stopped at, for
2175
+ * instance.
2176
+ *
2177
+ * Every part of the event is SDK-authored classification or server-authored
2178
+ * identifiers, so forwarding it as it stands is safe. `scope` says which
2179
+ * part of the SDK the line came from, which is what to route or filter on.
2180
+ */
2181
+ error(event: () => SafeEvent): void;
2182
+ }
2183
+ /**
2184
+ * A value the SDK is willing to put in a structured field above `'debug'`.
2185
+ *
2186
+ * Scalars only, and that is the redaction boundary made structural rather than
2187
+ * documentary. Every leak ADR-8 found was an *object* spread onto a line that
2188
+ * a host forwards to a tracker - an operation's params, a thrown wallet error,
2189
+ * a response body. None of them is assignable to this, so each is now a
2190
+ * compile error rather than a review catch.
2191
+ *
2192
+ * It does not stop a call site writing `{ token: accessToken }`, and it is not
2193
+ * meant to: a string is a string. What it stops is the mistake that actually
2194
+ * happened.
2195
+ */
2196
+ type LogValue = string | number | boolean | null;
2197
+ /**
2198
+ * Fields safe at every level: SDK-authored classification and server-minted
2199
+ * identifiers, constrained to {@link LogValue}.
2200
+ */
2201
+ type SafeFields = Readonly<Record<string, LogValue>>;
2202
+ /**
2203
+ * Fields for `'debug'` only: bodies, headers, full URLs, operation params,
2204
+ * raw thrown values. Arbitrary values, because that is the point of the level.
2205
+ *
2206
+ * The SDK's own implementations render these into a form that cannot fail to
2207
+ * serialize - a `bigint`, a cycle or a getter that throws must never turn a
2208
+ * log line into a second failure - but they are otherwise passed through.
2209
+ */
2210
+ type UnredactedFields = Readonly<Record<string, unknown>>;
2211
+ /**
2212
+ * One narrowing applied to a logger by {@link InternalLogger.child}.
2213
+ *
2214
+ * A union of single-key objects rather than a positional `string`, because the
2215
+ * three things the SDK narrows by are not the same kind of thing: a request id
2216
+ * correlates retries of one wire call, a flow name names an on-chain
2217
+ * procedure, a hook name names a React binding. Rendered into one `[SCOPE:x]`
2218
+ * prefix they were indistinguishable; as fields they must not be.
2219
+ *
2220
+ * `requestId` keeps its {@link RequestId} newtype rather than widening to
2221
+ * `string` at the seam.
2222
+ */
2223
+ type LogBinding = {
2224
+ readonly requestId: RequestId;
2225
+ } | {
2226
+ readonly flow: string;
2227
+ } | {
2228
+ readonly hook: string;
2229
+ } | {
2230
+ readonly op: string;
2231
+ };
2232
+ /**
2233
+ * Every {@link LogBinding} applied so far, merged. This is what reaches a
2234
+ * host, so a line from `prepareOndoSell` carries `flow: 'prepareSell'` as its
2235
+ * own key and is filterable without parsing anything.
2236
+ */
2237
+ type LogBindings = {
2238
+ readonly [K in LogBinding as keyof K]?: K[keyof K];
2239
+ };
2240
+ type BaseEvent = {
2241
+ /**
2242
+ * What happened, as a **constant string literal**.
2243
+ *
2244
+ * Never interpolated. A log aggregator groups by message, so a `msg` that
2245
+ * embeds a status, a count or a reason produces one group per distinct
2246
+ * value and is not groupable at all. Every varying value belongs in
2247
+ * `fields`, which is what a host filters and facets on.
2248
+ *
2249
+ * This is a convention, not a checked rule: deslop reasons about import
2250
+ * edges and cannot see a template literal, and the type-level approximations
2251
+ * catch only some interpolations. ADR-8 records why a partial guarantee was
2252
+ * refused here as elsewhere.
2253
+ */
2254
+ readonly msg: string;
2255
+ /**
2256
+ * Which part of the SDK reported this. Stamped by
2257
+ * {@link internalLogger}, never by a call site.
2258
+ */
2259
+ readonly scope: LogScope;
2260
+ /** Every {@link LogBinding} applied by {@link InternalLogger.child}. */
2261
+ readonly bindings: LogBindings;
2262
+ };
2263
+ /**
2264
+ * An event for `'info'`, `'warn'` and `'error'` - the levels a host may run in
2265
+ * production.
2266
+ *
2267
+ * Its fields are {@link SafeFields}, so nothing but a scalar can reach it.
2268
+ */
2269
+ type SafeEvent = BaseEvent & {
2270
+ readonly fields: SafeFields;
2271
+ /**
2272
+ * What the SDK knows about an accompanying failure, classified. Absent when
2273
+ * there is nothing to classify.
2274
+ */
2275
+ readonly cause?: LogCause;
2276
+ };
2277
+ /**
2278
+ * An event for `'debug'`, the unredacted level. Its fields accept anything.
2279
+ */
2280
+ type DebugEvent = BaseEvent & {
2281
+ readonly fields: UnredactedFields;
2282
+ };
2283
+ /**
2284
+ * How much the SDK may report, from silent to everything.
2285
+ *
2286
+ * Ordered: each level admits itself and everything before it, so `'info'`
2287
+ * shows errors, warnings and info but not debug. `'none'` admits nothing.
2288
+ *
2289
+ * `'debug'` is the unredacted level, and it is deliberately the bottom of the
2290
+ * ladder: the guarantee "`debug` is unredacted and every other level is not"
2291
+ * only closes if there is nothing below it. That is why the SDK keeps its own
2292
+ * five levels rather than adopting pino's seven, which put `trace` underneath.
2293
+ */
2294
+ type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug';
2295
+ /**
2296
+ * The redacted levels: the only ones to consider running in production.
2297
+ *
2298
+ * Every level except `'debug'`, which is unredacted by design. Used by the
2299
+ * shipped pino implementations to make a production `'debug'` logger
2300
+ * unrepresentable rather than merely discouraged - see
2301
+ * {@link PinoLoggerOptions}.
2302
+ *
2303
+ * The name marks which levels are *redacted*, not a verdict that running them
2304
+ * in production is safe. The SDK's advice is to leave {@link Config.logger}
2305
+ * undefined there; this type is what stops the partner who does not take it
2306
+ * from reaching the unredacted level as well.
2307
+ */
2308
+ type ProductionLogLevel = Exclude<LogLevel, 'debug'>;
2309
+ /**
2310
+ * How to build one of the SDK's pino-backed loggers.
2311
+ *
2312
+ * A union rather than `{ level, isDev }`, and that is the whole point:
2313
+ * `{ isDev: false, level: 'debug' }` does not typecheck, so a production
2314
+ * build cannot ship a logger that prints bearer tokens to a browser console.
2315
+ *
2316
+ * `isDev` does exactly one thing - decide whether `'debug'` is a legal level.
2317
+ * It changes no formatting: dev output and production output are the same
2318
+ * shape, so what you debug is what you ship. Pretty-printing is a pipe the
2319
+ * host owns (`node server.js | npx pino-pretty`), not a dependency the SDK
2320
+ * takes.
2321
+ */
2322
+ type PinoLoggerOptions = {
2323
+ readonly isDev: true;
2324
+ readonly level: LogLevel;
2325
+ } | {
2326
+ readonly isDev: false;
2327
+ readonly level: ProductionLogLevel;
2328
+ };
2329
+ /**
2330
+ * Which part of the SDK a line came from, carried as the `scope` field on
2331
+ * every event.
2332
+ *
2333
+ * It names the seam that **reported** the line, not the layer that raised what
2334
+ * it reports: an error from `OFFERS` surfaced by `useOffers` is reported on a
2335
+ * `HOOKS` line, because that is the hook telling you what it did with it. The
2336
+ * `OFFERS` line for the same error is emitted separately, by the namespace.
2337
+ *
2338
+ * A closed union rather than a free string so that `ONDO` and `Ondo` cannot
2339
+ * both appear, so that adding a namespace without giving it a scope fails to
2340
+ * compile, and so that a host can `switch` on it when routing. Anything finer
2341
+ * - an operation, a flow, a hook - is a {@link LogBinding} and appears as its
2342
+ * own field.
2343
+ */
2344
+ type LogScope = 'HTTP' | 'RPC' | 'AUTH' | 'OFFERS' | 'REQUIREMENTS' | 'WALLETS' | 'ERC20' | 'TOKEN_SALE' | 'SUPERSTATE' | 'ONDO' | 'CENTRIFUGE' | 'TOKENS' | 'SUPPORT' | 'HOOKS';
2345
+ /**
2346
+ * Correlates every line one logical request produces. The first attempt, each
2347
+ * retry, and the re-send after a session renewal all carry the same id, so a
2348
+ * failure can be read back to the request that caused it.
2349
+ */
2350
+ type RequestId = Newtype<string, 'RequestId'>;
2351
+ /**
2352
+ * The id frontline keys its own logs by, minted server-side and returned as
2353
+ * `event_id` on an error envelope. Quote it when raising a ticket with
2354
+ * CoinList support: it is what leads from a partner's log line to the request
2355
+ * as the backend saw it.
2356
+ *
2357
+ * A {@link Newtype} rather than a bare `string` because it sits next to
2358
+ * {@link RequestId} and frontline's `code` on the same cause, and the three
2359
+ * are not interchangeable: an id swapped for a code reads plausibly and points
2360
+ * a support engineer at nothing.
2361
+ */
2362
+ type FrontlineEventId = Newtype<string, 'FrontlineEventId'>;
2363
+ /**
2364
+ * What the SDK knows about a failure, classified.
2365
+ *
2366
+ * **Safe to forward as it stands.** Every arm is SDK-authored classification
2367
+ * or a server-authored identifier: no request or response body, no host-
2368
+ * supplied parameter, and no value read off a thrown error the SDK did not
2369
+ * author. There is nothing here to strip before handing it to an error
2370
+ * tracker, which is deliberate - a `LogCause` reaches {@link Logger.error},
2371
+ * the level a host actually runs in production, and a guarantee that depends
2372
+ * on the host remembering to strip a field is not a guarantee.
2373
+ *
2374
+ * It is the SDK's **only** structured classification of a thrown error, and it
2375
+ * reaches {@link Logger.warn} as well as {@link Logger.error}: a recoverable
2376
+ * failure is no less worth naming than a fatal one, and rendering it into a
2377
+ * string instead would have meant interpolating into `msg`.
2378
+ *
2379
+ * Bodies are a `'debug'`-only disclosure and stay on the `HTTP` lines, which
2380
+ * {@link RequestId} points at.
2381
+ *
2382
+ * The arms are the remedies, not the exception classes: a `validation` cause
2383
+ * means the backend sent something the SDK does not understand and is worth
2384
+ * reporting to CoinList, while an `invariant` cause means the SDK itself
2385
+ * computed something impossible and is worth reporting as an SDK bug. Reading
2386
+ * `type` should tell you who has to fix it.
2387
+ *
2388
+ * It carries no scope of its own. The {@link SafeEvent} it arrives on has one,
2389
+ * and a cause could only ever have repeated it: the scope is stamped by the
2390
+ * logger that *emits* the line, not by the layer that raised the error, so a
2391
+ * `validation` failure from `OFFERS` surfaced by `useOffers` would have been
2392
+ * tagged `HOOKS` on the hook's line. Forward the event, not the cause alone.
2393
+ */
2394
+ /**
2395
+ * Why an on-chain read failed, named after what a caller would do about it
2396
+ * rather than after the viem error class that reported it. See the `rpc` arm
2397
+ * of {@link LogCause}.
2398
+ */
2399
+ type RpcFailureReason = 'reverted' | 'node-rejected' | 'transport' | 'malformed-response' | 'unknown';
2400
+ type LogCause =
2401
+ /**
2402
+ * A request reached the backend and came back non-2xx.
2403
+ *
2404
+ * Deliberately carries no response body. `requestId` is how you get the
2405
+ * rest: the `HTTP` lines for the same request carry the method, the path,
2406
+ * the duration, every retry, and at `'debug'` the body itself. `code` and
2407
+ * `eventId` are frontline's own - quote `eventId` when raising a ticket with
2408
+ * CoinList support.
2409
+ */
2410
+ {
2411
+ type: 'http';
2412
+ requestId: RequestId | null;
2413
+ status: number;
2414
+ code: string | null;
2415
+ eventId: FrontlineEventId | null;
2416
+ }
2417
+ /**
2418
+ * The wire succeeded; the payload was not a shape the SDK can map.
2419
+ *
2420
+ * `message` is an SDK-authored template and may quote the single scalar that
2421
+ * failed - `Unsupported chain: "solana_mainnet"`, `expires_at: not a date
2422
+ * ("13/40/2026")`. That one value is the point of the arm; it is never a
2423
+ * body and never a whole field set.
2424
+ */
2425
+ | {
2426
+ type: 'validation';
2427
+ message: string;
2428
+ }
2429
+ /**
2430
+ * The SDK computed something impossible. An SDK bug - please report it.
2431
+ * `message` is an SDK-authored template, like `validation`'s.
2432
+ */
2433
+ | {
2434
+ type: 'invariant';
2435
+ message: string;
2436
+ }
2437
+ /**
2438
+ * Every operand was well-formed and the arithmetic on them still had no
2439
+ * answer: a division by zero, a result outside the range its type can hold.
2440
+ * `message` is an SDK-authored template, like `validation`'s.
2441
+ *
2442
+ * Separate from `invariant` because the remedy differs. An `invariant` says
2443
+ * the SDK computed something impossible and is a bug to report against the
2444
+ * SDK; a `math` says the operation was well-posed and the operands, which
2445
+ * came off the wire, admit no result - so it points at the response the same
2446
+ * way `validation` does, one step later.
2447
+ */
2448
+ | {
2449
+ type: 'math';
2450
+ message: string;
2451
+ }
2452
+ /** An API-backed call was made without a logged-in user. */
2453
+ | {
2454
+ type: 'not-authenticated';
2455
+ }
2456
+ /** A code path the SDK has not shipped yet. */
2457
+ | {
2458
+ type: 'not-implemented';
2459
+ }
2460
+ /**
2461
+ * The user's wallet, or the chain, refused.
2462
+ *
2463
+ * {@link RedactedWalletError} rather than the full `WalletError`: its
2464
+ * `unknown` arm arrives with its `cause` dropped, that field holding the raw
2465
+ * wallet-library error whose message and metadata carry the transaction's
2466
+ * `from`, `to`, `value` and calldata. It is still on the flow's own returned
2467
+ * result, and on the `'debug'` line the flow seam emits beside this one.
2468
+ *
2469
+ * A type rather than a scrub applied at the call site, so an arm that grew a
2470
+ * raw field would fail the build.
2471
+ */
2472
+ | {
2473
+ type: 'wallet';
2474
+ error: RedactedWalletError;
2475
+ }
2476
+ /**
2477
+ * An on-chain read did not answer. `reason` names the remedy, not the viem
2478
+ * class that carried it:
2479
+ *
2480
+ * - `reverted` - the contract refused the call. Check the arguments, or the
2481
+ * contract's own state (a stopped swap reverts its `preview`).
2482
+ * - `node-rejected` - the node reached the contract and declined. Usually a
2483
+ * malformed request, a rate limit, or a method the node does not serve.
2484
+ * Also where a host-supplied `Transport` throwing something viem cannot
2485
+ * read a code off lands: `buildRequest` wraps it in `UnknownRpcError`.
2486
+ * - `transport` - the node was not reached at all. Retry, or fail over.
2487
+ * A dropped `webSocket()` socket included.
2488
+ * - `malformed-response` - the call returned something the ABI cannot
2489
+ * decode, which in practice means the wrong address or a stale ABI.
2490
+ * - `unknown` - anything else. Rare, since viem wraps most of what a read
2491
+ * can throw into one of the classes the arms above name.
2492
+ *
2493
+ * Decided by `instanceof` alone. Nothing is read off the thrown error - not
2494
+ * its `name`, not its `message`, not a revert reason - because a viem
2495
+ * message embeds the calldata, the contract address and the caller, and
2496
+ * `name` is a writable property a host-supplied transport could set to
2497
+ * anything. All of that is on the paired `'debug'` line.
2498
+ */
2499
+ | {
2500
+ type: 'rpc';
2501
+ reason: RpcFailureReason;
2502
+ }
2503
+ /**
2504
+ * Something the SDK does not recognise - usually a host-supplied lambda
2505
+ * (`getAccessToken`, an {@link EvmWallet} method) throwing, or a
2506
+ * network-level `fetch` failure.
2507
+ *
2508
+ * `name` is the error's class name and nothing else. The message and the
2509
+ * thrown value itself are host-authored, so they are a `'debug'` disclosure:
2510
+ * the paired debug line renders them in full.
2511
+ */
2512
+ | {
2513
+ type: 'generic-error';
2514
+ name: string;
3034
2515
  };
2516
+
3035
2517
  /**
3036
- * Everything a user must satisfy before they can participate in an offer:
3037
- * reading the checklist and its live statuses, and the operations that satisfy
3038
- * individual requirements — identity verification (KYC) and tax-document
3039
- * signing.
2518
+ * Where the SDK sends its on-chain reads, per chain.
3040
2519
  *
3041
- * Wallet requirements (`external_wallet`, `whitelisted_wallet`) are satisfied
3042
- * through `WalletsNamespace` instead, since wallet proofs are also used
3043
- * outside the requirements flow.
2520
+ * Leave it undefined and reads go through CoinList's authenticated JSON-RPC
2521
+ * proxy (`POST /v1/chains/{chain}/rpc`), which needs no setup but does need a
2522
+ * logged-in user - the proxy is OAuth-gated, so a read on a chain the SDK is
2523
+ * proxying fails with its `401`, surfaced as the viem error wrapping it. A
2524
+ * chain you name here is read straight from your own node instead, and needs
2525
+ * no CoinList session at all.
3044
2526
  *
3045
- * Every method requires an authenticated user and throws
3046
- * {@link NotAuthenticatedError} otherwise.
2527
+ * Each entry is either:
2528
+ *
2529
+ * - an RPC URL, which the SDK wraps in viem's `http()` transport, or
2530
+ * - a viem {@link Transport} used as it stands - `fallback([...])`,
2531
+ * `webSocket()`, or your app's own.
2532
+ *
2533
+ * Chains you leave out fall back to the proxy individually, so overriding
2534
+ * mainnet does not opt Sepolia out. The SDK always constructs the viem
2535
+ * `PublicClient` itself, so the chain definition, batching and retry policy
2536
+ * stay the SDK's regardless of the transport you supply.
2537
+ *
2538
+ * @example
2539
+ * ```ts
2540
+ * createCoinListClient({
2541
+ * clientId,
2542
+ * redirectUri,
2543
+ * rpc: {
2544
+ * ethereum_mainnet: 'https://eth-mainnet.g.alchemy.com/v2/KEY',
2545
+ * ethereum_sepolia: fallback([http(primary), http(backup)]),
2546
+ * // base_* unset -> proxied through CoinList
2547
+ * },
2548
+ * });
2549
+ * ```
3047
2550
  */
3048
- interface RequirementsNamespace {
3049
- /**
3050
- * Fetches the requirements for every option of an offer, grouped by option
3051
- * id. This is the definition of the checklist; {@link statuses} tells you
3052
- * where the user stands against it.
3053
- */
3054
- forOffer(offerId: OfferId): Promise<Record<OfferOptionId, Requirement[]>>;
3055
- /**
3056
- * Fetches the current user's status for each requirement of an offer.
3057
- */
3058
- statuses(offerId: OfferId): Promise<RequirementStatusInfo[]>;
2551
+ type RpcConfig = Partial<Record<EthereumChain, string | Transport>>;
2552
+ interface Config {
2553
+ /** OAuth2 public identifier. */
2554
+ readonly clientId: ClientId;
3059
2555
  /**
3060
- * Creates a short-lived Sumsub WebSDK access token so an identity
3061
- * verification (KYC) flow can be started, e.g. by the `IdentityVerification`
3062
- * component.
2556
+ * OAuth2 redirect URI. Recommended to point to a frontend page where
2557
+ * {@link CoinListClient#completeOauth} can be called to complete the PKCE
2558
+ * flow on the client side.
3063
2559
  */
3064
- createKycToken(params?: CreateKycTokenParams): Promise<KycToken>;
2560
+ readonly redirectUri: RedirectUri;
3065
2561
  /**
3066
- * Fetches the current user's PII, used to pre-fill tax forms such as the
3067
- * W-8BEN. Fields the entity hasn't provided are `null`.
2562
+ * Recommended to leave undefined. Used to change the CoinList environment;
2563
+ * default is production.
3068
2564
  */
3069
- getPii(): Promise<Pii>;
2565
+ readonly baseUrl?: string;
3070
2566
  /**
3071
- * Starts (or resumes) a document signing submission.
2567
+ * Recommended to leave undefined. Overrides the base URL of the public
2568
+ * token registry backing `coinlist.tokens`; default is the production
2569
+ * registry.
3072
2570
  */
3073
- submitDocument(params: SubmitDocumentParams): Promise<DocumentSubmission>;
3074
- }
3075
- declare class RequirementsNamespaceImpl implements RequirementsNamespace {
3076
- protected readonly ctx: UniversalNamespaceContext;
3077
- protected readonly log: InternalLogger;
3078
- constructor(ctx: UniversalNamespaceContext);
3079
- forOffer(offerId: OfferId): Promise<Record<OfferOptionId, Requirement[]>>;
3080
- statuses(offerId: OfferId): Promise<RequirementStatusInfo[]>;
3081
- createKycToken(params?: CreateKycTokenParams): Promise<KycToken>;
3082
- getPii(): Promise<Pii>;
3083
- submitDocument(params: SubmitDocumentParams): Promise<DocumentSubmission>;
3084
- }
3085
-
3086
- /**
3087
- * Reads over CoinList's offers: the catalogue a user can browse, and the full
3088
- * detail of a single offer.
3089
- *
3090
- * Every method requires an authenticated user and throws
3091
- * {@link NotAuthenticatedError} otherwise. On the server, the same reads are
3092
- * additionally available with an app-level token — see
3093
- * `ServerOffersNamespace`.
3094
- */
3095
- interface OffersNamespace {
2571
+ readonly tokensBaseUrl?: string;
3096
2572
  /**
3097
- * Fetches every offer, iterating through all pages. Prefer {@link listPage}
3098
- * when you render a paginated list yourself.
2573
+ * Where the SDK reports what it is doing. **Omit it and the SDK logs nothing
2574
+ * at all** - no `console` fallback, at any level, on any codepath. Supply
2575
+ * one and every request, every classified failure and every hook state
2576
+ * transition is reported at the level your logger asks for.
2577
+ *
2578
+ * Absent, the SDK says nothing at all - there is no fallback to `console`,
2579
+ * at any level, on any codepath.
2580
+ *
2581
+ * **Running the SDK's logging in production is not advised.** The safest
2582
+ * posture is to leave this undefined outside development, staging and
2583
+ * incident reproduction: a seam that emits nothing cannot disclose anything,
2584
+ * and that property does not depend on the SDK continuing to get redaction
2585
+ * right.
2586
+ *
2587
+ * If you do run one there, run it at `'info'` or above and know what that
2588
+ * does and does not buy you. Those levels are **redacted by construction**:
2589
+ * they carry only SDK-authored classification and server-authored
2590
+ * identifiers, and the type system holds that line rather than a convention -
2591
+ * an event at those levels accepts scalar fields only, so a body, a DTO or
2592
+ * an operation's parameters cannot be put on one. What the SDK does **not**
2593
+ * give you is a warranty that the result is safe for your environment. The
2594
+ * mechanism is checkable and stated; the conclusion depends on your sink,
2595
+ * your retention and your threat model, and it is yours to draw.
2596
+ *
2597
+ * **`'debug'` is not.** It reports request and response bodies, full URLs,
2598
+ * headers and operation parameters verbatim - bearer tokens, KYC answers,
2599
+ * tax-document fields, wallet signatures - and **the SDK does not redact**.
2600
+ * Run it on a developer's machine, in tests, and in beta or staging
2601
+ * environments where the data flowing through is not real customer data.
2602
+ * The shipped implementations make that structural: built with
2603
+ * `isDev: false`, `'debug'` does not typecheck. Either way, filtering,
2604
+ * redaction and retention at your sink are yours, not the SDK's.
2605
+ *
2606
+ * Every method you implement here **must be total**: the SDK calls them on
2607
+ * the codepath of the work they report and does not catch them, so a logger
2608
+ * that throws fails the operation it was describing.
2609
+ *
2610
+ * See {@link Logger} for the full contract, and {@link pinoClientLogger} or
2611
+ * {@link pinoServerLogger} for a ready-made implementation over pino.
3099
2612
  */
3100
- list(): Promise<Offer[]>;
2613
+ readonly logger?: Logger;
3101
2614
  /**
3102
- * Fetches a single page of offers. Pass the previous response's
3103
- * `startingAfter` as `after` to advance.
2615
+ * Where on-chain reads go. Omit it and they are proxied through CoinList.
2616
+ * See {@link RpcConfig}.
3104
2617
  */
3105
- listPage(params: PaginationParams): Promise<PaginatedResponse<Offer>>;
2618
+ readonly rpc?: RpcConfig;
3106
2619
  /**
3107
- * Fetches the full detail of one offer, including its options. Note this
3108
- * returns {@link OfferDetail} — a richer model than the {@link Offer}
3109
- * summaries {@link list} returns.
2620
+ * Called when the SDK has **confirmed** that the user's CoinList session is
2621
+ * gone - its refresh token expired or was revoked - so you can clear your
2622
+ * own state and show a sign-in entry point.
2623
+ *
2624
+ * Confirmed, because the backend answers "rotated a moment ago by a
2625
+ * concurrent request" and "dead" identically. A single rejection is only a
2626
+ * suspicion: the SDK acts on it only once the same session is rejected again
2627
+ * after a settle window, so a lost refresh race never signs a user out.
2628
+ *
2629
+ * Each environment reports what it confirmed itself, so it fires in the
2630
+ * one that drove the confirmation:
2631
+ *
2632
+ * - **`CoinListClient`**: fires when the SDK moves from `'logged-in'` to
2633
+ * `'logged-out'` because your `getAccessToken` answered `null` (or the
2634
+ * very token the API just refused) twice, a second apart.
2635
+ * - **`CoinListServer`**: fires when the same process sees a refresh token
2636
+ * rejected twice, 5 to 60 seconds apart, or an expired session that
2637
+ * carries no refresh token. The SDK clears the session from your
2638
+ * `SessionStore` just before. The browser's confirmation, through your
2639
+ * `getAccessToken` endpoint, comes a second after the first rejection, so
2640
+ * it does not reach this verdict: when the browser drives renewal, expect
2641
+ * the `CoinListClient` callback, and this one only if a later request
2642
+ * presents the dead session 5 to 60 seconds on.
2643
+ *
2644
+ * It never fires on `auth.logout()`, on a 5xx, a timeout or a network
2645
+ * failure - those mean the SDK could not find out, not that the session
2646
+ * ended. Per environment it fires at most once per session, within a
2647
+ * 60-second memory: the server remembers a verdict for 60 seconds per
2648
+ * process (per JavaScript module instance, so Next.js middleware and route
2649
+ * handlers count separately). The same dead session presented again after
2650
+ * that, or to another instance, can be reported again, so make your handler
2651
+ * idempotent.
2652
+ *
2653
+ * It is called on the next microtask and not awaited. A rejection is
2654
+ * logged and dropped, so it cannot fail or delay the request that
2655
+ * discovered the expiry. A synchronous handler is written `async () => {}`.
3110
2656
  */
3111
- get(id: OfferId): Promise<OfferDetail>;
3112
- }
3113
- declare class OffersNamespaceImpl implements OffersNamespace {
3114
- private readonly ctx;
3115
- private readonly log;
3116
- constructor(ctx: UniversalNamespaceContext);
3117
- list(): Promise<Offer[]>;
3118
- listPage(params: PaginationParams): Promise<PaginatedResponse<Offer>>;
3119
- get(id: OfferId): Promise<OfferDetail>;
2657
+ readonly onSessionExpired?: () => Promise<void>;
3120
2658
  }
3121
2659
 
3122
- /**
3123
- * Raw JSON from the token registry (Nabu), a static CDN serving token display
3124
- * metadata. It is a separate backend from frontline: responses omit optional
3125
- * fields rather than sending `null`, and may grow unknown fields at any time;
3126
- * we only read the fields named here.
3127
- */
3128
- type NabuLogoImageDto = {
3129
- url: string;
3130
- width: number;
3131
- height: number;
3132
- };
3133
- /**
3134
- * A logo is either a single vector (valid at any size) or a dimensioned
3135
- * raster original plus pre-scaled variants at ascending widths.
3136
- */
3137
- type NabuLogoDto = {
3138
- kind: 'VECTOR';
3139
- url: string;
3140
- } | {
3141
- kind: 'RASTER';
3142
- original: NabuLogoImageDto;
3143
- variants: NabuLogoImageDto[];
3144
- };
3145
- /** One token from the registry's `/{chain}/token/{address}` route. */
3146
- type NabuTokenDto = {
3147
- data_version: string;
3148
- schema_version: number;
3149
- chain: string;
3150
- protocol: string;
3151
- chain_id: number;
3152
- kind: string;
3153
- name: string;
3154
- symbol: string;
3155
- decimals: number;
3156
- logo: NabuLogoDto;
3157
- logo_dark?: NabuLogoDto;
3158
- coingecko_id?: string;
3159
- teller_code?: string;
3160
- address: string;
2660
+ type ClientCredentialsOAuth = Newtype<OAuthAccessToken, 'ClientCredentialsOAuth'>;
2661
+ declare const ClientCredentialsOAuth: (value: OAuthAccessToken) => ClientCredentialsOAuth;
2662
+ type OAuthAccessToken = {
2663
+ value: string;
2664
+ expiresAt: Date;
3161
2665
  };
3162
- /**
3163
- * One asset inside a chain snapshot. The snapshot mixes the chain's native
3164
- * coin (`kind: 'COIN'`, no address) with its tokens (`kind: 'TOKEN'`), so
3165
- * `address` is optional here where {@link NabuTokenDto} requires it.
3166
- */
3167
- type NabuChainAssetDto = {
3168
- kind: string;
3169
- name: string;
3170
- symbol: string;
3171
- decimals: number;
3172
- logo: NabuLogoDto;
3173
- logo_dark?: NabuLogoDto;
3174
- address?: string;
3175
- };
3176
- /** The registry's `/{chain}/assets.json` route: one chain, all its assets. */
3177
- type NabuChainAssetsDto = {
3178
- data_version: string;
3179
- schema_version: number;
3180
- chain: string;
3181
- protocol: string;
3182
- assets: NabuChainAssetDto[];
3183
- };
3184
- /** The registry's `/assets.json` route: the complete snapshot, every chain. */
3185
- type NabuRegistryDto = {
3186
- data_version: string;
3187
- schema_version: number;
3188
- chains: Array<{
3189
- chain: string;
3190
- protocol: string;
3191
- assets: NabuChainAssetDto[];
3192
- }>;
2666
+ type OAuthRefreshToken = Newtype<string, 'OAuthRefreshToken'>;
2667
+ declare const OAuthRefreshToken: (value: string) => OAuthRefreshToken;
2668
+ type OAuthSession = {
2669
+ accessToken: OAuthAccessToken;
2670
+ refreshToken?: OAuthRefreshToken;
3193
2671
  };
3194
2672
 
3195
2673
  /**
3196
- * An absolute URL to a logo image in the token registry. Registry image URLs
3197
- * are content-fingerprinted and served immutable, so a value is safe to cache
3198
- * for as long as you hold it.
3199
- */
3200
- type TokenLogoUrl = Newtype<string, 'TokenLogoUrl'>;
3201
- declare const TokenLogoUrl: (value: string) => TokenLogoUrl;
3202
- type TokenLogoImage = {
3203
- url: TokenLogoUrl;
3204
- width: number;
3205
- height: number;
3206
- };
3207
- /**
3208
- * `VECTOR` is a single SVG. `RASTER` has webp variants at 32/64/128/256/512px
3209
- * widths (never wider than the original): use the smallest one that covers
3210
- * your render size, or `original` if none does.
3211
- */
3212
- type TokenLogo = {
3213
- kind: 'VECTOR';
3214
- url: TokenLogoUrl;
3215
- } | {
3216
- kind: 'RASTER';
3217
- original: TokenLogoImage;
3218
- variants: TokenLogoImage[];
3219
- };
3220
- declare const TokenLogo: {
3221
- /** `baseUrl` is the registry origin; registry URLs are root-relative. */
3222
- fromDto: (dto: NabuLogoDto, baseUrl: string) => TokenLogo;
3223
- };
3224
- /**
3225
- * Display metadata for one token, from CoinList's public token registry.
3226
- *
3227
- * Keyed by `(chain, address)` — never by symbol, which can collide. The
3228
- * registry is curated: a token missing from it is an expected answer, not an
3229
- * error, so lookups return `null` rather than throwing.
2674
+ * An instant: milliseconds since the Unix epoch, as `Date.now()` reads it.
2675
+ * Distinct from {@link Millis}, a duration, so an instant cannot be passed
2676
+ * where a timeout is expected or the other way round.
3230
2677
  */
3231
- type TokenMetadata = {
3232
- identifier: TokenIdentifier;
3233
- name: string;
3234
- symbol: AssetSymbol;
3235
- decimals: AssetDecimals;
3236
- logo: TokenLogo;
3237
- /** Dark-theme logo; `null` when the registry configures none. */
3238
- logoDark: TokenLogo | null;
3239
- };
3240
- declare const TokenMetadata: {
3241
- /** `baseUrl` is the registry origin; registry logo URLs are root-relative. */
3242
- fromDto: (dto: NabuTokenDto, baseUrl: string) => TokenMetadata;
3243
- /**
3244
- * Maps the complete registry snapshot to every token it lists across the
3245
- * chains this SDK models, skipping native coins and chains outside
3246
- * {@link EthereumChain} (e.g. Solana) rather than failing on them — the
3247
- * registry may serve chains ahead of the SDK's type surface.
3248
- */
3249
- fromRegistryDto: (dto: NabuRegistryDto, baseUrl: string) => TokenMetadata[];
2678
+ type EpochMillis = Newtype<number, 'EpochMillis'>;
2679
+ declare const EpochMillis: {
3250
2680
  /**
3251
- * Maps a chain snapshot to the tokens it lists, skipping the chain's native
3252
- * coin (`kind: 'COIN'`, no contract address).
2681
+ * @throws {@link ValidationError} unless `value` is an integer a `Date` can
2682
+ * represent: at most {@link MAX_EPOCH_MILLIS} either side of the epoch.
3253
2683
  */
3254
- fromChainAssetsDto: (dto: NabuChainAssetsDto, baseUrl: string) => TokenMetadata[];
2684
+ of: (value: number) => EpochMillis;
2685
+ /** The wall clock. Code that needs the time takes a `TimeEff` instead. */
2686
+ now: () => EpochMillis;
2687
+ /** @throws {@link ValidationError} for an invalid `Date`. */
2688
+ fromDate: (date: Date) => EpochMillis;
2689
+ /** How long after `from` the instant `to` is; negative if it is before. */
2690
+ elapsed: (from: EpochMillis, to: EpochMillis) => Millis;
2691
+ plus: (at: EpochMillis, duration: Millis) => EpochMillis;
3255
2692
  };
3256
-
3257
2693
  /**
3258
- * Token display metadata — name, symbol, decimals, and logos — from
3259
- * CoinList's public token registry, keyed by {@link TokenIdentifier} (the
3260
- * same chain + address pairs `Offer.tokens` carries).
3261
- *
3262
- * Unlike the other namespaces, this one is public: no method requires an
3263
- * authenticated user, and nothing here touches the CoinList API — reads go to
3264
- * the registry's CDN.
2694
+ * A duration in milliseconds. May be negative: the gap between two instants
2695
+ * read off a wall clock that stepped back in between.
3265
2696
  */
3266
- interface TokensNamespace {
3267
- /**
3268
- * Fetches metadata for one token. Returns `null` when the registry does not
3269
- * list the token — the registry is curated, so callers must fall back to
3270
- * their own display defaults rather than treat this as an error.
3271
- */
3272
- get(token: TokenIdentifier): Promise<TokenMetadata | null>;
3273
- /**
3274
- * Fetches every token the registry lists, in one request: the complete
3275
- * snapshot with no `chain`, or one chain's snapshot with it. Prefer this
3276
- * over calling {@link get} in a loop when displaying a catalogue — two
3277
- * hundred tokens across three chains is still a single download.
3278
- *
3279
- * The complete snapshot spans every chain the registry knows; tokens on
3280
- * chains this SDK does not model (e.g. Solana) are left out of the result.
3281
- *
3282
- * Unlike {@link get}, a missing snapshot throws rather than returning `[]`:
3283
- * the registry always publishes the complete snapshot and one per chain it
3284
- * knows, so an absence is a deployment problem, not an empty catalogue.
3285
- */
3286
- list(chain?: EthereumChain): Promise<TokenMetadata[]>;
3287
- }
3288
- declare class TokensNamespaceImpl implements TokensNamespace {
3289
- private readonly api;
3290
- private readonly log;
2697
+ type Millis = Newtype<number, 'Millis'>;
2698
+ declare const Millis: {
2699
+ of: (value: number) => Millis;
2700
+ };
2701
+ /** A duration in whole, non-negative seconds. */
2702
+ type Seconds = Newtype<number, 'Seconds'>;
2703
+ declare const Seconds: {
3291
2704
  /**
3292
- * Takes the registry origin rather than a `UniversalNamespaceContext`: the
3293
- * registry is unauthenticated and on its own host, so the frontline sender
3294
- * and the auth check would both be dead weight here.
2705
+ * @throws {@link ValidationError} unless `value` is a non-negative integer.
3295
2706
  */
3296
- constructor(baseUrl: string, logger?: Logger | null);
3297
- get(token: TokenIdentifier): Promise<TokenMetadata | null>;
3298
- list(chain?: EthereumChain): Promise<TokenMetadata[]>;
3299
- }
2707
+ of: (value: number) => Seconds;
2708
+ toMillis: (value: Seconds) => Millis;
2709
+ };
3300
2710
 
3301
- export { OndoTradingStatus as $, AuthorizationCode as A, BlockchainAmount as B, CodeVerifier as C, TokenMetadata as D, type Erc20Namespace as E, type OfferType as F, type OndoSwapOfferDetail as G, type CoinListTokenSaleOfferDetail as H, type SuperstateSwapOfferDetail as I, Offer as J, RequirementStatusInfo as K, type Logger as L, type RequirementType as M, type RequirementStatusValue as N, OfferId as O, Participation as P, OfferOptionAddress as Q, type RequirementsNamespace as R, type SuperstateSwapNamespace as S, type TokensNamespace as T, type UniversalNamespaceContext as U, RequirementId as V, type WalletError as W, type KycLevelName as X, DocumentSubmission as Y, type WalletChallengeType as Z, type PinoLoggerOptions as _, EthereumChain as a, type GetTokenBalanceParams as a$, AssetDecimals as a0, type BuildOndoBuyParams as a1, type BuildOndoSellParams as a2, OndoQuote as a3, type OndoQuoteSize as a4, type TokenIdentifier as a5, OfferOptionAddressId as a6, TokenLogo as a7, type DebugEvent as a8, type FrontlineEventId as a9, type Newtype as aA, type AllowWalletParams as aB, AllowWalletResponse as aC, Asset as aD, AssetCode as aE, Blockchain as aF, type BuildOndoSwapParamsCore as aG, Chain as aH, ClientId as aI, CodeChallenge as aJ, type CoinListTokenSaleOffer as aK, ConnectExternalWalletParams as aL, type CreateKycTokenParams as aM, CreateParticipationParams as aN, CreateWalletOwnershipChallengeParams as aO, Cursor as aP, type DocumentFormType as aQ, type DocumentSubmissionStatus as aR, type DocumentType as aS, ETHEREUM_CHAINS as aT, Erc20NamespaceImpl as aU, FaqItem as aV, type GetOndoQuoteParams as aW, type GetOndoTradingStatusParams as aX, type GetSwapAuthorizationParams as aY, type GetSwapPreviewParams as aZ, type GetTokenAllowanceParams as a_, HttpError as aa, type HttpResponse as ab, KycToken as ac, type LogBinding as ad, type LogBindings as ae, type LogCause as af, type LogLevel as ag, type LogScope as ah, type LogValue as ai, type ProductionLogLevel as aj, RedactedWalletError as ak, type RequestId as al, type RpcFailureReason as am, type SafeEvent as an, type SafeFields as ao, type UnredactedFields as ap, OAuthSession as aq, ClientCredentialsOAuth as ar, ClientSecret as as, type Sender as at, PaginationParams as au, PaginatedResponse as av, DecimalString as aw, SwapStatus as ax, type Uint256 as ay, KnownAssetSymbol as az, EvmContractAddress as b, HexEncodedTransactionData as b0, Isin as b1, Iso2CountryCode as b2, Link as b3, type ListOptionAddressesParams as b4, MAX_ASSET_DECIMALS as b5, MAX_UINT_256 as b6, Milestone as b7, OAuthRefreshToken as b8, type OfferBase as b9, SOLANA_CHAINS as bA, STABLE_DECIMALS as bB, SolanaChain as bC, type SubmitDocumentParams as bD, type SuperstateSwapOffer as bE, type SwapContractRef as bF, SwapPreview as bG, TermItem as bH, Ticker as bI, type TokenLogoImage as bJ, TokenLogoUrl as bK, type TokenRole as bL, TokensNamespaceImpl as bM, type Tx as bN, WalletAddress as bO, WalletOwnershipChallenge as bP, type WalletProtocol as bQ, WalletsNamespaceImpl as bR, apiErrorCode as bS, assertUint256 as bT, isChain as bU, isOfferType as bV, parseUint256 as bW, type OfferDetailBase as ba, OfferEditorial as bb, OfferOption as bc, OfferOptionSlug as bd, OfferSlug as be, OfferToken as bf, OffersNamespaceImpl as bg, type OndoQuoteDuration as bh, type OndoSellOutcome as bi, type OndoSwapOffer as bj, PKCEState as bk, type PaginatedResponseDto as bl, ParticipationId as bm, type ParticipationStatus as bn, ParticipationsPaginationParams as bo, PauseFlags as bp, Pii as bq, PiiAddress as br, PiiJurisdiction as bs, type PiiKind as bt, type QueryParamValue as bu, type QueryParamValues as bv, RedirectUri as bw, type RemoveOptionAddressParams as bx, type RequirementActionNeededReason as by, type RpcConfig as bz, type CoinListTokenSaleNamespace as c, OfferOptionId as d, AssetId as e, CoinListTokenSaleNamespaceImpl as f, type OndoNamespace as g, AssetSymbol as h, OfferSwapContract as i, OndoBuyTransaction as j, OndoSellTransaction as k, type OndoSwapTransactionCore as l, type OrderBookSide as m, OndoNamespaceImpl as n, type WalletsNamespace as o, Bps as p, EvmWalletAddress as q, SuperstateSwapNamespaceImpl as r, Requirement as s, RequirementsNamespaceImpl as t, type Config as u, type OAuthAccessToken as v, type OffersNamespace as w, StablecoinSymbol as x, type Erc20Asset as y, OfferDetail as z };
2711
+ export { type CentrifugeVaultStatus as $, AuthorizationCode as A, BlockchainAmount as B, CodeVerifier as C, type Offer as D, EthereumChain as E, type TokenMetadata as F, type OfferDetail as G, StablecoinSymbol as H, type Erc20Asset as I, type CoinListTokenSaleOfferDetail as J, CentrifugeVaultLegId as K, type CentrifugeVaultOfferDetail as L, Seconds as M, AsyncOfferDetail as N, type OfferVaultContract as O, type Participation as P, type RequirementStatusInfo as Q, type RequirementsNamespace as R, type SwapQuote as S, type TokensNamespace as T, type KycLevelName as U, type RequirementType as V, type WalletError as W, type RequirementStatusValue as X, RequirementId as Y, type PinoLoggerOptions as Z, type Logger as _, AssetSymbol as a, type FrontlineEventId as a$, type CentrifugeVaultRef as a0, type BuildCentrifugeVaultBuyParams as a1, type BuildOndoBuyParams as a2, type OndoSwapOfferDetail as a3, type BuildOndoSellParams as a4, type OndoQuote as a5, type OndoQuoteSize as a6, type OndoTradingStatus as a7, AssetDecimals as a8, type SuperstateSwapOfferDetail as a9, type CentrifugeDepositPosition as aA, type CentrifugeOrderReason as aB, type CentrifugeOrderability as aC, type CentrifugeRedeemPosition as aD, CentrifugeShares as aE, type CentrifugeVaultClaimProceedsTransaction as aF, type CentrifugeVaultClaimTransaction as aG, type CentrifugeVaultOffer as aH, type CentrifugeVaultReason as aI, type CentrifugeVaultResolution as aJ, type CentrifugeVaultTransactionCore as aK, type CentrifugeWalletParams as aL, Chain as aM, type CoinListTokenSaleOffer as aN, type ConnectExternalWalletParams as aO, type CreateKycTokenParams as aP, type CreateParticipationParams as aQ, type CreateWalletOwnershipChallengeParams as aR, Cursor as aS, type DebugEvent as aT, DecimalString as aU, type DocumentFormType as aV, type DocumentSubmissionStatus as aW, type DocumentType as aX, ETHEREUM_CHAINS as aY, EpochMillis as aZ, type FaqItem as a_, CentrifugeVaultLeg as aa, type CentrifugeVault as ab, OfferOptionAddressId as ac, type DocumentSubmission as ad, type TokenIdentifier as ae, type OAuthSession as af, ClientCredentialsOAuth as ag, type PaginationParams as ah, type PaginatedResponse as ai, ClientSecret as aj, type RequestId as ak, ClientId as al, RedirectUri as am, CodeChallenge as an, PKCEState as ao, type SwapStatus as ap, KnownAssetSymbol as aq, type AllowWalletParams as ar, type AllowWalletResponse as as, type Asset as at, AssetCode as au, type AsyncOfferType as av, type BaseEvent as aw, Blockchain as ax, type BuildOndoSwapParamsCore as ay, CentrifugeAssets as az, EvmContractAddress as b, type TokenLogo as b$, type GetCentrifugeVaultStatusParams as b0, type GetOndoQuoteParams as b1, type GetOndoTradingStatusParams as b2, type GetSwapAuthorizationParams as b3, type GetSwapPreviewParams as b4, type GetSwapQuoteParams as b5, type GetTokenAllowanceParams as b6, type GetTokenBalanceParams as b7, HexEncodedTransactionData as b8, Isin as b9, type OndoSwapOffer as bA, ParticipationId as bB, type ParticipationStatus as bC, type ParticipationsPaginationParams as bD, PauseFlags as bE, type Pii as bF, type PiiAddress as bG, type PiiJurisdiction as bH, type PiiKind as bI, type ProductionLogLevel as bJ, type ReadCentrifugePositionParams as bK, type RedactedWalletError as bL, type RemoveOptionAddressParams as bM, type RequirementActionNeededReason as bN, type RpcConfig as bO, type RpcFailureReason as bP, SOLANA_CHAINS as bQ, STABLE_DECIMALS as bR, type SafeEvent as bS, type SafeFields as bT, SolanaChain as bU, type SubmitDocumentParams as bV, type SuperstateSwapOffer as bW, type SwapContractRef as bX, type SwapPreview as bY, type TermItem as bZ, Ticker as b_, Iso2CountryCode as ba, type KycToken as bb, type Link as bc, type ListOptionAddressesParams as bd, type LogBinding as be, type LogBindings as bf, type LogCause as bg, type LogLevel as bh, type LogScope as bi, type LogValue as bj, MAX_ASSET_DECIMALS as bk, MAX_UINT_256 as bl, type Milestone as bm, Millis as bn, type Newtype as bo, OAuthRefreshToken as bp, type OfferBase as bq, type OfferDetailBase as br, type OfferEditorial as bs, type OfferOption as bt, OfferOptionSlug as bu, OfferSlug as bv, type OfferToken as bw, type OfferType as bx, type OndoQuoteDuration as by, type OndoSellOutcome as bz, type CentrifugeVaultBuyTransaction as c, type TokenLogoImage as c0, TokenLogoUrl as c1, type TokenRole as c2, type Tx as c3, type Uint256 as c4, type UnredactedFields as c5, WalletAddress as c6, type WalletOwnershipChallenge as c7, type WalletOwnershipChallengeParamsBase as c8, type WalletProtocol as c9, assertUint256 as ca, isChain as cb, isOfferType as cc, parseUint256 as cd, resolveCentrifugeVaults as ce, type CentrifugeClaimDirection as d, type CentrifugeNamespace as e, OfferId as f, OfferOptionId as g, AssetId as h, type CoinListTokenSaleNamespace as i, type OfferSwapContract as j, type OndoBuyTransaction as k, type OndoSellTransaction as l, type OndoNamespace as m, type OndoSwapTransactionCore as n, type OrderBookSide as o, Bps as p, EvmWalletAddress as q, type SuperstateSwapNamespace as r, type Requirement as s, type WalletChallengeType as t, type OfferOptionAddress as u, type WalletsNamespace as v, type Config as w, type OAuthAccessToken as x, type OffersNamespace as y, type Erc20Namespace as z };