@coinlist-co/react 0.12.2-rc.7b430dd → 0.12.2-rc.a59b344

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 (33) hide show
  1. package/dist/{chunk-DCW7NQXH.js → chunk-4IZ2F732.js} +52 -19
  2. package/dist/chunk-4IZ2F732.js.map +1 -0
  3. package/dist/{chunk-MOE5S6CE.js → chunk-NBO5E2RO.js} +152 -6
  4. package/dist/chunk-NBO5E2RO.js.map +1 -0
  5. package/dist/{chunk-VH34YC3Q.js → chunk-UJYSXXJF.js} +168 -3
  6. package/dist/chunk-UJYSXXJF.js.map +1 -0
  7. package/dist/client/index.cjs +469 -145
  8. package/dist/client/index.cjs.map +1 -1
  9. package/dist/client/index.d.cts +114 -35
  10. package/dist/client/index.d.ts +114 -35
  11. package/dist/client/index.js +297 -114
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/server/index.cjs +507 -116
  14. package/dist/server/index.cjs.map +1 -1
  15. package/dist/server/index.d.cts +65 -41
  16. package/dist/server/index.d.ts +65 -41
  17. package/dist/server/index.js +321 -65
  18. package/dist/server/index.js.map +1 -1
  19. package/dist/time-CFocgYyl.d.cts +50 -0
  20. package/dist/time-DvgN98Q9.d.ts +50 -0
  21. package/dist/{token-registry-snapshot-BNBNuDu1.d.ts → token-registry-snapshot-BsAfBL_b.d.ts} +9 -7
  22. package/dist/{token-registry-snapshot-CqI6dXM_.d.cts → token-registry-snapshot-Cc_eA5zM.d.cts} +9 -7
  23. package/dist/{tokens-namespace-CIWBtgt2.d.cts → tokens-namespace-D_XSpiS-.d.cts} +154 -19
  24. package/dist/{tokens-namespace-CIWBtgt2.d.ts → tokens-namespace-D_XSpiS-.d.ts} +154 -19
  25. package/dist/universal/index.cjs +333 -40
  26. package/dist/universal/index.cjs.map +1 -1
  27. package/dist/universal/index.d.cts +358 -5
  28. package/dist/universal/index.d.ts +358 -5
  29. package/dist/universal/index.js +28 -2
  30. package/package.json +1 -1
  31. package/dist/chunk-DCW7NQXH.js.map +0 -1
  32. package/dist/chunk-MOE5S6CE.js.map +0 -1
  33. package/dist/chunk-VH34YC3Q.js.map +0 -1
@@ -1,13 +1,15 @@
1
1
  import {
2
2
  Attributes,
3
+ EpochMillis,
3
4
  HEADER_IDEMPOTENCY_KEY,
4
5
  HttpClient,
5
6
  HttpError,
6
7
  Request,
8
+ TokenTrigger,
7
9
  getUUIDv4,
8
10
  internalLogger,
9
11
  viemChain
10
- } from "./chunk-MOE5S6CE.js";
12
+ } from "./chunk-NBO5E2RO.js";
11
13
 
12
14
  // src/universal/api/nabu/config.ts
13
15
  var NABU_BASE_URL = "https://asset.coinlist.co";
@@ -135,6 +137,12 @@ function transportFor(api, override, chain, logger) {
135
137
  return typeof override === "string" ? http(override) : override;
136
138
  }
137
139
 
140
+ // src/universal/core/shared/effects.ts
141
+ var systemTime = { time: { now: EpochMillis.now } };
142
+ var systemDelay = {
143
+ delay: (duration) => new Promise((resolve) => setTimeout(resolve, duration))
144
+ };
145
+
138
146
  // src/universal/core/shared/observability/pino-logger.ts
139
147
  function toPinoRecord(event) {
140
148
  try {
@@ -267,23 +275,33 @@ function attachSessionMiddleware(fetchAccessToken) {
267
275
  if (!Attributes.isProtected(request.attributes)) {
268
276
  return request;
269
277
  }
270
- let accessToken = await fetchAccessToken(false);
271
- if (!accessToken) {
272
- const clientCreds = Attributes.getClientCredentials(request.attributes);
273
- if (clientCreds) {
274
- accessToken = clientCreds;
275
- }
278
+ const userToken = await fetchAccessToken(TokenTrigger.beforeRequest);
279
+ if (userToken !== null) {
280
+ return withBearer(
281
+ {
282
+ ...request,
283
+ attributes: Attributes.concat(
284
+ request.attributes ?? Attributes.empty,
285
+ Attributes.sentWith(userToken)
286
+ )
287
+ },
288
+ userToken.value
289
+ );
276
290
  }
277
- if (accessToken === null) {
278
- return request;
291
+ const clientCreds = Attributes.getClientCredentials(request.attributes);
292
+ if (clientCreds) {
293
+ return withBearer(request, clientCreds.value);
294
+ }
295
+ return request;
296
+ };
297
+ }
298
+ function withBearer(request, token) {
299
+ return {
300
+ ...request,
301
+ headers: {
302
+ ...request.headers ?? {},
303
+ Authorization: `Bearer ${token}`
279
304
  }
280
- return {
281
- ...request,
282
- headers: {
283
- ...request.headers ?? {},
284
- Authorization: `Bearer ${accessToken.value}`
285
- }
286
- };
287
305
  };
288
306
  }
289
307
 
@@ -319,7 +337,7 @@ function getRetryDelayMs(attempt) {
319
337
  function createRequestRetryMiddleware(options = {}) {
320
338
  const delayFn = options.delayFn ?? defaultDelay;
321
339
  return async ({ request, response, retry }) => {
322
- if (!isRetryableStatus(response.status)) {
340
+ if (!isRetryableStatus(response.status) || !Attributes.isRetryable(request.attributes)) {
323
341
  return response;
324
342
  }
325
343
  const currentAttempt = Attributes.getRetryAttempt(request.attributes);
@@ -352,7 +370,9 @@ function renewSessionMiddleware(fetchAccessToken) {
352
370
  if (Attributes.wasRenewAttempted(request.attributes)) {
353
371
  return response;
354
372
  }
355
- const newToken = await fetchAccessToken(true);
373
+ const newToken = await fetchAccessToken(
374
+ TokenTrigger.after401(Attributes.getSentWith(request.attributes))
375
+ );
356
376
  if (newToken) {
357
377
  const nextRequest = Request.concatAttributes(
358
378
  request,
@@ -394,12 +414,25 @@ var AuthenticatedApiClient = class {
394
414
  }
395
415
  };
396
416
 
417
+ // src/universal/core/auth/session-expired.ts
418
+ function notifySessionExpired(onSessionExpired, log) {
419
+ if (onSessionExpired === void 0) {
420
+ return;
421
+ }
422
+ Promise.resolve().then(onSessionExpired).catch(
423
+ (error) => log.failure(() => ({ msg: "onSessionExpired threw" }), error)
424
+ );
425
+ }
426
+
397
427
  export {
398
428
  AuthenticatedApiClient,
429
+ notifySessionExpired,
399
430
  NABU_BASE_URL,
400
431
  publicClients,
432
+ systemTime,
433
+ systemDelay,
401
434
  PINO_LEVEL,
402
435
  SDK_LOGGER_NAME,
403
436
  loggerOverPino
404
437
  };
405
- //# sourceMappingURL=chunk-DCW7NQXH.js.map
438
+ //# sourceMappingURL=chunk-4IZ2F732.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/universal/api/nabu/config.ts","../src/universal/api/rpc/public-clients.ts","../src/universal/api/rpc/frontline-transport.ts","../src/universal/core/shared/effects.ts","../src/universal/core/shared/observability/pino-logger.ts","../src/universal/api/middleware/attach-session-middleware.ts","../src/universal/api/middleware/idempotency-key.ts","../src/universal/api/middleware/request-retry.ts","../src/universal/api/middleware/session-renewal.ts","../src/universal/api/authenticated-api-client.ts","../src/universal/core/auth/session-expired.ts"],"sourcesContent":["/**\n * Base URL of CoinList's public token registry (Nabu), a static CDN serving\n * token display metadata keyed by chain + contract address. Override per\n * environment via `Config.tokensBaseUrl`.\n */\nexport const NABU_BASE_URL = 'https://asset.coinlist.co';\n","import {\n createPublicClient,\n http,\n type PublicClient,\n type Transport,\n} from 'viem';\nimport type { Sender } from '@/universal/api/http-client';\nimport { frontlineTransport } from '@/universal/api/rpc/frontline-transport';\nimport { viemChain } from '@/universal/core/shared/blockchain/chain';\nimport type { EthereumChain } from '@/universal/types/blockchain/core';\nimport type { RpcConfig } from '@/universal/types/config';\nimport type { Logger } from '@/universal/types/logger';\n\n/**\n * Resolves a chain to the viem `PublicClient` the SDK reads it through.\n *\n * One function rather than a client per read, because the client is where the\n * batching lives: viem's multicall scheduler is keyed on the client's `uid`,\n * so two reads only ever share an `eth_call` if they share a client. Hence the\n * memoisation - it is correctness for batching, not a performance tweak.\n */\nexport type PublicClients = (chain: EthereumChain) => PublicClient;\n\n/**\n * Builds the per-chain `PublicClient` factory, lazily and once per chain.\n *\n * `batch: { multicall: true }` routes concurrent `eth_call`s through\n * Multicall3, whose address comes from viem's own chain definitions (see\n * `viemChain`). A checkout screen mounts several hooks that each read\n * independently - status, authorization, allowance, balance - and they collapse\n * into a single request. Per-call failures survive it: viem calls `aggregate3`\n * with `allowFailure: true` and re-throws each revert on its own call.\n *\n * **A read reached through these clients must take its subject as an\n * argument.** Batching makes `msg.sender` the Multicall3 address rather than\n * the caller, so a view that reads `msg.sender` would answer differently under\n * batching than without. Nothing the SDK reads does - `allowance(owner,\n * spender)`, `balanceOf(account)`, `authorized(user)` all name theirs - and a\n * new read must keep it that way.\n */\nexport function publicClients(\n api: Sender,\n config: RpcConfig | undefined,\n logger: Logger | null = null\n): PublicClients {\n const cache = new Map<EthereumChain, PublicClient>();\n\n return (chain) => {\n const cached = cache.get(chain);\n if (cached) return cached;\n\n const client = createPublicClient({\n chain: viemChain(chain),\n transport: transportFor(api, config?.[chain], chain, logger),\n batch: { multicall: true },\n });\n cache.set(chain, client);\n return client;\n };\n}\n\n/**\n * A host's override, or CoinList's proxy. A string is a URL the SDK wraps in\n * `http()`; a {@link Transport} is used as it stands, so `fallback([...])` and\n * a partner's own transport both pass through untouched.\n *\n * Only the proxy transport is given the logger. A host's own transport reports\n * however they built it, and viem's `http()` has no logging seam, so a host on\n * their own node gets whatever they wired up and nothing from the SDK.\n */\nfunction transportFor(\n api: Sender,\n override: string | Transport | undefined,\n chain: EthereumChain,\n logger: Logger | null\n): Transport {\n if (override === undefined) return frontlineTransport(api, chain, logger);\n return typeof override === 'string' ? http(override) : override;\n}\n","import { custom, RpcRequestError, type Transport } from 'viem';\nimport { HttpError } from '@/universal/api/http';\nimport { Attributes } from '@/universal/api/http-attributes';\nimport type { Sender } from '@/universal/api/http-client';\nimport {\n type InternalLogger,\n internalLogger,\n} from '@/universal/core/shared/observability/internal-logger';\nimport type { EthereumChain } from '@/universal/types/blockchain/core';\nimport type { Logger } from '@/universal/types/logger';\n\n/**\n * A viem {@link Transport} that speaks JSON-RPC to CoinList's proxy,\n * `POST /v1/chains/{chain}/rpc`.\n *\n * Built over the SDK's own {@link Sender} rather than viem's `http()`, so an\n * on-chain read is an SDK request like any other: it carries the bearer token,\n * renews a stale session on a 401 and retries, is stamped with a request id,\n * and is reported by `HttpClient` by construction. Pointing `http()` at the\n * same URL would mean a second HTTP path through the SDK with none of that.\n * Retrying is the `Sender`'s job alone: viem's own `retryCount` is set to `0`\n * here, because it would otherwise rerun that whole pipeline on top of itself.\n *\n * **This is where the session requirement lives**, and it is the proxy that\n * enforces it: the endpoint is OAuth-gated, so a logged-out read comes back\n * `401` and reaches the caller as viem's `ContractFunctionExecutionError`\n * wrapping the SDK's own `HttpError`. Deliberately *not* translated to\n * `NotAuthenticatedError` - viem re-wraps whatever a `Transport` throws, so the\n * translation would not survive to the caller anyway and would only buy a\n * promise the SDK cannot keep. `classifyLogCause` walks the chain, so the\n * failure still logs as the HTTP failure it is.\n *\n * A partner who supplies their own node via {@link RpcConfig} never reaches\n * this transport and correctly needs no CoinList session.\n *\n * **What its `RPC` lines add over the `HTTP` ones for the same request.** The\n * JSON-RPC method, which the path `/v1/chains/{chain}/rpc` does not carry; and\n * a JSON-RPC error, which usually arrives with HTTP 200 and is therefore an\n * `info` success as far as `HttpClient` can tell.\n *\n * **They do not name the contract call**, and cannot: `publicClients` batches\n * through Multicall3, so one `eth_call` here may be several reads and its `to`\n * is Multicall3 rather than any contract. What is available is on the `debug`\n * line - the raw params, so the target address and the calldata. Which method\n * asked for the read, and whether it reverted, come from the namespace above.\n */\nexport function frontlineTransport(\n api: Sender,\n chain: EthereumChain,\n logger: Logger | null = null\n): Transport {\n const log = internalLogger(logger, 'RPC');\n let nextId = 0;\n\n return custom(\n {\n async request({ method, params }) {\n const id = ++nextId;\n return sendRpc({ api, log, chain, id, method, params });\n },\n },\n // The `Sender` below already retries: `requestRetryMiddleware` makes three\n // attempts at a 5xx and `renewSessionMiddleware` re-sends once after a 401.\n // viem's own default is `retryCount: 3`, and it applies on top: an\n // `HttpError` carries no numeric `code`, so `buildRequest` wraps it in\n // `UnknownRpcError` (`code === -1`) and `shouldRetry` says yes. That reruns\n // the whole pipeline four times - twelve requests for one `eth_call`, and\n // with multicall batching one `eth_call` is a screen's worth of reads. The\n // SDK's retry policy is the one that applies, so viem's is turned off.\n { retryCount: 0 }\n );\n}\n\nasync function sendRpc({\n api,\n log,\n chain,\n id,\n method,\n params,\n}: {\n api: Sender;\n log: InternalLogger;\n chain: EthereumChain;\n /** The JSON-RPC envelope's `id`, unique per request within one transport. */\n id: number;\n method: string;\n /** The JSON-RPC method's own params, passed through untouched. */\n params: unknown;\n}): Promise<unknown> {\n const url = rpcPath(chain);\n const startedAt = Date.now();\n\n // `params` carries calldata, so the envelope is a `'debug'` disclosure.\n log.debug(() => ({\n msg: 'rpc call sent',\n fields: { id, method, chain, params },\n }));\n\n const response = await sendEnvelope(api, url, {\n jsonrpc: '2.0',\n id,\n method,\n params,\n });\n const elapsed = Date.now() - startedAt;\n\n // A JSON-RPC error usually arrives with HTTP 200, so the `Sender` hands it\n // back as a success. viem's own `http()` transport raises `RpcRequestError`\n // here, and every viem action above - `readContract`'s revert decoding\n // included - is written against that.\n if (response.error) {\n const error = new RpcRequestError({\n body: { method, params },\n error: response.error,\n url,\n });\n // `warn`: the node declined, and only the namespace above knows whether\n // that broke anything. The code is the node's own, not a message anyone\n // wrote about this request.\n //\n // The cause is stated rather than classified. viem's `buildRequest` maps a\n // `RpcRequestError` onto one of its `RpcError` subclasses on the way out,\n // which is what `classifyLogCause` recognises - but that happens *above*\n // this throw, so classifying here would answer `'unknown'` for every\n // rejection. This call site knows: the node answered with an error object.\n log.warning(\n () => ({\n msg: 'rpc call rejected',\n cause: { type: 'rpc', reason: 'node-rejected' },\n fields: {\n method,\n chain,\n duration_ms: elapsed,\n 'rpc.error_code': response.error?.code ?? null,\n },\n }),\n error\n );\n throw error;\n }\n\n log.info(() => ({\n msg: 'rpc call',\n fields: { method, chain, duration_ms: elapsed },\n }));\n return response.result;\n}\n\nfunction rpcPath(chain: EthereumChain): string {\n return `/v1/chains/${encodeURIComponent(chain)}/rpc`;\n}\n\n/**\n * Posts the envelope, and reads a JSON-RPC error out of a non-2xx as well.\n *\n * The proxy forwards the node's status alongside its body, so a node that\n * answers a JSON-RPC error with, say, a `400` reaches the `Sender` as an\n * {@link HttpError}. Left as that, the node's error code and any revert data\n * would be lost behind an opaque HTTP failure. An `HttpError` whose body is not\n * a JSON-RPC error - frontline's own `422` for an unsupported chain or a\n * disallowed method, a `401`, a `5xx` - is the HTTP failure it looks like and\n * propagates untouched.\n */\nasync function sendEnvelope(\n api: Sender,\n url: string,\n body: { jsonrpc: '2.0'; id: number; method: string; params: unknown }\n): Promise<JsonRpcResponse> {\n try {\n return await api.send<JsonRpcResponse>({\n method: 'POST',\n url,\n body,\n attributes: Attributes.protected(),\n });\n } catch (error) {\n if (error instanceof HttpError && isJsonRpcError(error.response.body)) {\n return error.response.body;\n }\n throw error;\n }\n}\n\nfunction isJsonRpcError(\n body: unknown\n): body is Required<Pick<JsonRpcResponse, 'error'>> {\n if (typeof body !== 'object' || body === null || !('error' in body)) {\n return false;\n }\n const { error } = body;\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof error.code === 'number'\n );\n}\n\n/**\n * Only what the transport branches on. The SDK does not model the envelope as\n * a DTO: it is JSON-RPC's shape, not frontline's, and every field but these\n * two is handed straight back to viem.\n */\ntype JsonRpcResponse = {\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n};\n","import { EpochMillis, type Millis } from '@/universal/types/time';\n\n/**\n * Reading the clock, as an effect: code that takes a `TimeEff` can be driven\n * by a fake clock in specs instead of real time.\n */\nexport type TimeEff = {\n readonly time: { readonly now: () => EpochMillis };\n};\n\n/** Waiting, as an effect, for the same reason as {@link TimeEff}. */\nexport type DelayEff = {\n readonly delay: (duration: Millis) => Promise<void>;\n};\n\n/** The real clock, wired in at the construction edges. */\nexport const systemTime: TimeEff = { time: { now: EpochMillis.now } };\n\n/** Real waiting on `setTimeout`, wired in at the construction edges. */\nexport const systemDelay: DelayEff = {\n delay: (duration) => new Promise((resolve) => setTimeout(resolve, duration)),\n};\n","import type {\n DebugEvent,\n Logger,\n LogLevel,\n SafeEvent,\n} from '@/universal/types/logger';\n\n/**\n * The SDK's logging seam expressed over pino, minus the environment.\n *\n * Imports no pino - {@link PinoSink} is a structural type - so `@/universal`\n * stays pino-free and the `./universal` entry point costs a consumer nothing.\n * Both shipped implementations\n * ({@link pinoClientLogger}, {@link pinoServerLogger}) differ only in the pino\n * instance they construct; everything worth testing lives here.\n *\n * ## Nothing here may throw\n *\n * A {@link Logger} implementation is called inline, on the codepath of the\n * work it reports, and the SDK does not catch it - so a log line that fails to\n * render would turn a successful request into an exception. That risk is real\n * rather than theoretical: `'debug'` fields carry whatever the SDK was\n * handling, and this codebase routinely handles `bigint` (every uint256 on\n * every DTO), which `JSON.stringify` throws on. Response bodies may be\n * circular, params may hold functions, and a host's object may have a getter\n * that throws.\n *\n * So {@link toPinoRecord} is total by construction: every value is rendered\n * into something a JSON serialiser cannot refuse, every property read is\n * guarded, and the whole translation has a fallback. Pino is never handed a\n * value it could fail on.\n */\n\n/**\n * Flattens an event into the object pino merges into its output line.\n *\n * Flat rather than nested, because nesting is what a host has to un-nest\n * before they can facet on it: `scope`, the {@link LogBindings} and every\n * field sit at the top level, and only `cause` stays an object because its\n * arms are a closed union a host switches on.\n *\n * `msg` is **not** included: it is pino's own second argument.\n *\n * Never throws. A value it cannot render becomes a `<tag>` string rather than\n * an exception, and a translation that fails entirely degrades to a record\n * naming the failure.\n */\nexport function toPinoRecord(event: SafeEvent | DebugEvent): PinoRecord {\n try {\n const record: Record<string, unknown> = {};\n\n for (const key of ownKeys(event.fields)) {\n record[key] = readProperty(event.fields, key, 0, new WeakSet());\n }\n\n const cause = 'cause' in event ? event.cause : undefined;\n if (cause !== undefined) {\n record.cause = sanitize(cause, 0, new WeakSet());\n }\n\n // Last, so the stamped keys win. `scope` and the bindings are what a host\n // routes and filters on, and they are stamped by `internalLogger` rather\n // than written at a call site - so a field that happened to be named\n // `scope` or `op` must not be able to take their place.\n return { ...record, scope: event.scope, ...event.bindings };\n } catch (_) {\n // The event itself is malformed - a Proxy for `fields`, a frozen exotic\n // object. Report that rather than losing the line, and never rethrow.\n return { 'log.render': '<unrenderable event>' };\n }\n}\n\n/** What pino merges into one output line. */\nexport type PinoRecord = Readonly<Record<string, unknown>>;\n\n/**\n * The SDK's five levels in pino's vocabulary.\n *\n * `'none'` is pino's `'silent'`; the other four are the same word in both, and\n * deliberately so. The SDK does not adopt pino's seven because `'trace'` would\n * sit below `'debug'`, and the guarantee \"`debug` is unredacted and every\n * other level is not\" only closes while `'debug'` is the bottom.\n */\nexport const PINO_LEVEL = {\n none: 'silent',\n error: 'error',\n warn: 'warn',\n info: 'info',\n debug: 'debug',\n} as const satisfies Record<LogLevel, string>;\n\n/**\n * How deep a `'debug'` field is followed before it is summarised.\n *\n * A bound rather than a guess at what is deep enough: a self-referential\n * structure is already caught by the cycle check, but a legitimately deep DTO\n * would otherwise cost real time inside a log line.\n */\nconst MAX_DEPTH = 8;\n\n/**\n * Renders any value into something a JSON serialiser cannot refuse.\n *\n * `bigint` is the one that matters in practice - every uint256 the SDK handles\n * is one - but the rest are all reachable from a `'debug'` field: `undefined`\n * and functions from an operation's params, `NaN` from arithmetic, symbols\n * from a host's object.\n */\nfunction sanitize(\n value: unknown,\n depth: number,\n seen: WeakSet<object>\n): unknown {\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return value;\n case 'number':\n // `NaN` and the infinities serialise to `null`, which reads as \"absent\"\n // rather than \"not a number\". Name them instead.\n return Number.isFinite(value) ? value : String(value);\n case 'bigint':\n return value.toString();\n case 'undefined':\n return '<undefined>';\n case 'function':\n return '<function>';\n case 'symbol':\n return value.toString();\n case 'object':\n return value === null ? null : sanitizeObject(value, depth, seen);\n default:\n return '<unrenderable>';\n }\n}\n\nfunction sanitizeObject(\n value: object,\n depth: number,\n seen: WeakSet<object>\n): unknown {\n if (seen.has(value)) return '<circular>';\n if (depth >= MAX_DEPTH) return '<max depth>';\n\n // `name`, `message` and `stack` are all non-enumerable, so an `Error` handed\n // to a structured sink as an object serialises to `{}`.\n if (value instanceof Error) return describeError(value);\n if (value instanceof Date) return describeDate(value);\n\n seen.add(value);\n try {\n if (Array.isArray(value)) {\n return value.map((_item, index) =>\n readProperty(value, String(index), depth, seen)\n );\n }\n\n const out: Record<string, unknown> = {};\n for (const key of ownKeys(value)) {\n out[key] = readProperty(value, key, depth, seen);\n }\n return out;\n } finally {\n // Removed on the way out, so a value referenced twice in a tree renders\n // twice. Only a genuine cycle is `<circular>`.\n seen.delete(value);\n }\n}\n\n/**\n * One property read, guarded. A getter may throw, and a log line is not\n * allowed to be the reason a request fails.\n */\nfunction readProperty(\n owner: object,\n key: string,\n depth: number,\n seen: WeakSet<object>\n): unknown {\n try {\n return sanitize((owner as Record<string, unknown>)[key], depth + 1, seen);\n } catch (_) {\n return '<unreadable>';\n }\n}\n\n/** Own enumerable keys, guarded: an exotic object may refuse to be enumerated. */\nfunction ownKeys(value: object): readonly string[] {\n try {\n return Object.keys(value);\n } catch (_) {\n return [];\n }\n}\n\nfunction describeError(error: Error): Record<string, unknown> {\n return {\n name: safeRead(() => error.name),\n message: safeRead(() => error.message),\n stack: safeRead(() => error.stack),\n };\n}\n\nfunction describeDate(date: Date): unknown {\n // An invalid `Date` throws from `toISOString`.\n return safeRead(() => date.toISOString());\n}\n\nfunction safeRead(read: () => unknown): unknown {\n try {\n const value = read();\n return typeof value === 'string' ? value : '<unreadable>';\n } catch (_) {\n return '<unreadable>';\n }\n}\n\n/**\n * The part of a pino logger this adapter uses.\n *\n * Structural rather than `import type { Logger } from 'pino'`, so that\n * `@/universal` takes no dependency on pino at all. A real pino instance - node\n * or browser build - satisfies it.\n */\nexport type PinoSink = {\n debug(record: PinoRecord, msg: string): void;\n info(record: PinoRecord, msg: string): void;\n warn(record: PinoRecord, msg: string): void;\n error(record: PinoRecord, msg: string): void;\n};\n\n/**\n * The `name` binding every SDK line carries, so a partner can separate the\n * SDK's output from their own in a shared aggregator without matching on\n * `scope`.\n *\n * Applied through `pino.child({ name })` rather than pino's `name` *option*,\n * which its browser build ignores. The browser lane's spec is what caught\n * that, and is what keeps it caught.\n */\nexport const SDK_LOGGER_NAME = '@coinlist-co/react';\n\n/**\n * Adapts a pino instance into the {@link Logger} the SDK consumes.\n *\n * **Total, and that is the point.** The SDK calls a `Logger` inline and does\n * not catch it, so an exception raised while reporting a successful request\n * would fail that request. Three things could raise one - the event lambda\n * itself, rendering the event, and the sink - and all three are caught here.\n * A render failure is reported through the sink rather than swallowed, since a\n * log line that silently vanishes is a bug nobody finds; if the sink is what\n * broke, there is nowhere left to report it and the error stops here.\n *\n * **The level is fixed here, and that is this adapter's choice rather than the\n * seam's.** {@link InternalLogger} calls {@link Logger.level} before *every*\n * log call and caches nothing, so a host-implemented `Logger` reading a mutable\n * field has a level they can change at runtime. This adapter captures `level`\n * instead, and the pino instance it wraps is built with a fixed one - so\n * `Logger.level` is a constant lookup and two loggers never interfere. A host\n * who wants to turn `'debug'` on without rebuilding implements {@link Logger}\n * directly over their own pino instance; the port is four methods and this\n * function is the worked example.\n */\nexport function loggerOverPino(sink: PinoSink, level: LogLevel): Logger {\n return {\n level: () => level,\n debug: (event) => emit(sink, 'debug', event),\n info: (event) => emit(sink, 'info', event),\n warn: (event) => emit(sink, 'warn', event),\n error: (event) => emit(sink, 'error', event),\n };\n}\n\nfunction emit(\n sink: PinoSink,\n method: keyof PinoSink,\n event: () => SafeEvent | DebugEvent\n): void {\n try {\n const value = event();\n sink[method](toPinoRecord(value), value.msg);\n } catch (error) {\n reportRenderFailure(sink, error);\n }\n}\n\nfunction reportRenderFailure(sink: PinoSink, error: unknown): void {\n try {\n sink.error(\n { 'error.type': error instanceof Error ? error.name : typeof error },\n 'log event failed to render'\n );\n } catch (_) {\n // The sink itself threw. There is nowhere left to report this, and the SDK\n // is on the other side of the call that got us here.\n }\n}\n","import type { HttpRequest } from '@/universal/api/http';\nimport { Attributes } from '@/universal/api/http-attributes';\nimport type { BeforeRequestMiddleware } from '@/universal/api/http-client';\nimport {\n type InternalAccessTokenSource,\n TokenTrigger,\n} from '@/universal/api/internal-access-token-source';\n\nexport function attachSessionMiddleware(\n fetchAccessToken: InternalAccessTokenSource\n): BeforeRequestMiddleware {\n return async (request: HttpRequest): Promise<HttpRequest> => {\n if (!Attributes.isProtected(request.attributes)) {\n return request;\n }\n\n const userToken = await fetchAccessToken(TokenTrigger.beforeRequest);\n if (userToken !== null) {\n // Recorded so a 401 can name the token it refused: see `TokenTrigger`.\n return withBearer(\n {\n ...request,\n attributes: Attributes.concat(\n request.attributes ?? Attributes.empty,\n Attributes.sentWith(userToken)\n ),\n },\n userToken.value\n );\n }\n\n const clientCreds = Attributes.getClientCredentials(request.attributes);\n if (clientCreds) {\n return withBearer(request, clientCreds.value);\n }\n return request;\n };\n}\n\nfunction withBearer(request: HttpRequest, token: string): HttpRequest {\n return {\n ...request,\n headers: {\n ...(request.headers ?? {}),\n Authorization: `Bearer ${token}`,\n },\n };\n}\n","import { HEADER_IDEMPOTENCY_KEY } from '@/universal/api/frontline/config';\nimport type { HttpRequest } from '@/universal/api/http';\nimport { Attributes } from '@/universal/api/http-attributes';\nimport type { BeforeRequestMiddleware } from '@/universal/api/http-client';\nimport { getUUIDv4 } from '@/universal/core/shared/utils/crypto';\n\nexport const idempotencyKeyMiddleware: BeforeRequestMiddleware = async (\n request: HttpRequest\n): Promise<HttpRequest> => {\n if (!Attributes.isIdempotent(request.attributes)) {\n return request;\n }\n\n const existingHeaders = request.headers ?? {};\n if (existingHeaders[HEADER_IDEMPOTENCY_KEY]) {\n return request;\n }\n\n return {\n ...request,\n headers: {\n ...existingHeaders,\n [HEADER_IDEMPOTENCY_KEY]: getUUIDv4(),\n },\n };\n};\n","import { Request } from '@/universal/api/http';\nimport { Attributes } from '@/universal/api/http-attributes';\nimport type { AfterRequestMiddleware } from '@/universal/api/http-client';\n\nconst MAX_ATTEMPTS = 3;\nconst INITIAL_DELAY_MS = 300;\nconst MAX_DELAY_MS = 2000;\n\nconst RETRYABLE_4XX = new Set([408, 409, 429]);\n\nexport function isRetryableStatus(status: number): boolean {\n return (status >= 500 && status < 600) || RETRYABLE_4XX.has(status);\n}\n\nfunction getRetryDelayMs(attempt: number): number {\n return Math.min(INITIAL_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);\n}\n\nexport type RequestRetryMiddlewareOptions = {\n /**\n * Optional delay function. Defaults to real setTimeout-based delay.\n * Use a no-op (e.g. () => Promise.resolve()) in tests to avoid slow tests.\n */\n delayFn?: (ms: number) => Promise<void>;\n};\n\n/**\n * Creates the request retry after-request middleware. Inject a no-op delayFn\n * in tests to avoid real delays (unit testing best practice).\n */\nexport function createRequestRetryMiddleware(\n options: RequestRetryMiddlewareOptions = {}\n): AfterRequestMiddleware {\n const delayFn = options.delayFn ?? defaultDelay;\n\n return async ({ request, response, retry }) => {\n if (\n !isRetryableStatus(response.status) ||\n !Attributes.isRetryable(request.attributes)\n ) {\n return response;\n }\n\n const currentAttempt = Attributes.getRetryAttempt(request.attributes);\n if (currentAttempt >= MAX_ATTEMPTS - 1) {\n return response;\n }\n\n const nextAttempt = currentAttempt + 1;\n await delayFn(getRetryDelayMs(nextAttempt));\n\n const nextRequest = Request.concatAttributes(\n request,\n Attributes.retryAttempt(nextAttempt)\n );\n return retry(nextRequest);\n };\n}\n\nfunction defaultDelay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Retries the request up to 3 times with exponential backoff when the response\n * has a retryable status (5xx server errors). Uses request.attributes.retryAttempt\n * to decide delay and whether to retry, so the middleware does not loop when\n * retry() runs the full HTTP middleware chain again.\n */\nexport const requestRetryMiddleware: AfterRequestMiddleware =\n createRequestRetryMiddleware();\n","import { Request } from '@/universal/api/http';\nimport { Attributes } from '@/universal/api/http-attributes';\nimport type { AfterRequestMiddleware } from '@/universal/api/http-client';\nimport {\n type InternalAccessTokenSource,\n TokenTrigger,\n} from '@/universal/api/internal-access-token-source';\n\n/**\n * On a 401 to a protected request, asks for a better token than the one the\n * request was sent with and replays the request once with it.\n *\n * Deduplication is the token source's job, not this middleware's: a burst of\n * N requests that all 401 calls it N times, and the source answers all of\n * them from one renewal.\n */\nexport function renewSessionMiddleware(\n fetchAccessToken: InternalAccessTokenSource\n): AfterRequestMiddleware {\n return async ({ request, response, retry }) => {\n if (response.status !== 401) {\n return response;\n }\n if (!Attributes.isProtected(request.attributes)) {\n return response;\n }\n if (Attributes.wasRenewAttempted(request.attributes)) {\n return response;\n }\n\n const newToken = await fetchAccessToken(\n TokenTrigger.after401(Attributes.getSentWith(request.attributes))\n );\n if (newToken) {\n const nextRequest = Request.concatAttributes(\n request,\n Attributes.renewAttempted(true)\n );\n return retry(nextRequest);\n } else {\n return response;\n }\n };\n}\n","import type { HttpClientConfig, HttpRequest } from '@/universal/api/http';\nimport { HttpError } from '@/universal/api/http';\nimport type { BeforeRequestMiddleware } from '@/universal/api/http-client';\nimport { HttpClient } from '@/universal/api/http-client';\nimport type { InternalAccessTokenSource } from '@/universal/api/internal-access-token-source';\nimport { attachSessionMiddleware } from '@/universal/api/middleware/attach-session-middleware';\nimport { idempotencyKeyMiddleware } from '@/universal/api/middleware/idempotency-key';\nimport { requestRetryMiddleware } from '@/universal/api/middleware/request-retry';\nimport { renewSessionMiddleware } from '@/universal/api/middleware/session-renewal';\nimport type { Logger } from '@/universal/types/logger';\n\n/**\n * Isomorphic HTTP client with session attachment, session renewal on 401,\n * idempotency key injection, and request retry. Works in both browser and\n * Node/server environments. Pass `additionalBeforeRequest` to inject\n * environment-specific middleware (e.g. `userAgentMiddleware` on the client).\n *\n * Pass `logger` through to have every request reported; the client logs\n * nothing without one.\n */\nexport class AuthenticatedApiClient {\n private readonly httpClient: HttpClient;\n\n constructor(\n config: HttpClientConfig,\n fetchAccessToken: InternalAccessTokenSource,\n additionalBeforeRequest: BeforeRequestMiddleware[] = [],\n logger: Logger | null = null\n ) {\n this.httpClient = new HttpClient(\n config,\n {\n beforeRequest: [\n attachSessionMiddleware(fetchAccessToken),\n ...additionalBeforeRequest,\n idempotencyKeyMiddleware,\n ],\n afterRequest: [\n renewSessionMiddleware(fetchAccessToken),\n requestRetryMiddleware,\n ],\n },\n logger\n );\n }\n\n async send<TResponse>(request: HttpRequest): Promise<TResponse> {\n const response = await this.httpClient.send<TResponse>(request);\n if (response.status >= 200 && response.status < 300) {\n return response.body as TResponse;\n } else {\n throw new HttpError(response);\n }\n }\n}\n","import type { InternalLogger } from '@/universal/core/shared/observability/internal-logger';\n\n/**\n * Calls the host's `Config.onSessionExpired`, without awaiting it and without\n * letting it fail the request that found out.\n *\n * Total: the handler runs on the next microtask, so a sync throw from untyped\n * JavaScript and an async rejection land on the same `catch`, and both are\n * logged and dropped.\n */\nexport function notifySessionExpired(\n onSessionExpired: (() => Promise<void>) | undefined,\n log: InternalLogger\n): void {\n if (onSessionExpired === undefined) {\n return;\n }\n Promise.resolve()\n .then(onSessionExpired)\n .catch((error: unknown) =>\n log.failure(() => ({ msg: 'onSessionExpired threw' }), error)\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,3 +1,27 @@
1
+ // src/universal/api/internal-access-token-source.ts
2
+ var SentWith = {
3
+ userToken: (token) => ({
4
+ type: "user-token",
5
+ token
6
+ }),
7
+ noUserToken: { type: "no-user-token" }
8
+ };
9
+ var TokenTrigger = {
10
+ beforeRequest: { type: "before-request" },
11
+ after401: (rejected) => ({
12
+ type: "after-401",
13
+ rejected
14
+ })
15
+ };
16
+ function isRejectedToken(token, rejected) {
17
+ switch (rejected.type) {
18
+ case "user-token":
19
+ return rejected.token.value === token.value;
20
+ case "no-user-token":
21
+ return false;
22
+ }
23
+ }
24
+
1
25
  // src/universal/api/http-attributes.ts
2
26
  var empty = {};
3
27
  var concat = (left, right) => ({
@@ -25,6 +49,10 @@ var retryAttempt = (attempt) => ({
25
49
  var renewAttempted = (value) => ({
26
50
  renewAttempted: value
27
51
  });
52
+ var notRetryable = () => ({ retryable: false });
53
+ var sentWith = (token) => ({
54
+ sentWith: token
55
+ });
28
56
  var requestId = (id) => ({ requestId: id });
29
57
  var getRequestId = (attrs) => attrs?.requestId ?? null;
30
58
  var isProtected = (attrs) => attrs?.protected === true;
@@ -32,6 +60,8 @@ var needUserAgent = (attrs) => attrs?.userAgent === true;
32
60
  var isIdempotent = (attrs) => attrs?.idempotencyKey === true;
33
61
  var getRetryAttempt = (attrs) => attrs?.retryAttempt ?? 0;
34
62
  var wasRenewAttempted = (attrs) => attrs?.renewAttempted === true;
63
+ var isRetryable = (attrs) => attrs?.retryable !== false;
64
+ var getSentWith = (attrs) => attrs?.sentWith === void 0 ? SentWith.noUserToken : SentWith.userToken(attrs.sentWith);
35
65
  var getClientCredentials = (attrs) => attrs?.clientCredentials;
36
66
  var Attributes = {
37
67
  empty,
@@ -47,6 +77,10 @@ var Attributes = {
47
77
  getRetryAttempt,
48
78
  renewAttempted,
49
79
  wasRenewAttempted,
80
+ notRetryable,
81
+ isRetryable,
82
+ sentWith,
83
+ getSentWith,
50
84
  clientCredentials,
51
85
  getClientCredentials,
52
86
  requestId,
@@ -209,6 +243,54 @@ var MathError = class extends Error {
209
243
  }
210
244
  };
211
245
 
246
+ // src/universal/types/time.ts
247
+ var MAX_EPOCH_MILLIS = 864e13;
248
+ var EpochMillis = {
249
+ /**
250
+ * @throws {@link ValidationError} unless `value` is an integer a `Date` can
251
+ * represent: at most {@link MAX_EPOCH_MILLIS} either side of the epoch.
252
+ */
253
+ of: (value) => {
254
+ if (!Number.isInteger(value) || Math.abs(value) > MAX_EPOCH_MILLIS) {
255
+ throw new ValidationError(
256
+ `EpochMillis must be an integer within \xB1${MAX_EPOCH_MILLIS}, got ${value}`
257
+ );
258
+ }
259
+ return value;
260
+ },
261
+ /** The wall clock. Code that needs the time takes a `TimeEff` instead. */
262
+ now: () => EpochMillis.of(Date.now()),
263
+ /** @throws {@link ValidationError} for an invalid `Date`. */
264
+ fromDate: (date) => EpochMillis.of(date.getTime()),
265
+ /** How long after `from` the instant `to` is; negative if it is before. */
266
+ elapsed: (from, to) => Millis.of(to - from),
267
+ plus: (at, duration) => EpochMillis.of(at + duration)
268
+ };
269
+ var Millis = {
270
+ of: (value) => value
271
+ };
272
+ var Seconds = {
273
+ /**
274
+ * @throws {@link ValidationError} unless `value` is a non-negative integer.
275
+ */
276
+ of: (value) => {
277
+ if (!Number.isInteger(value) || value < 0) {
278
+ throw new ValidationError(
279
+ `Seconds must be a non-negative integer, got ${value}`
280
+ );
281
+ }
282
+ return value;
283
+ },
284
+ toMillis: (value) => Millis.of(value * 1e3)
285
+ };
286
+ function isExpired({
287
+ expiresAt,
288
+ now,
289
+ buffer = Millis.of(0)
290
+ }) {
291
+ return now >= expiresAt - buffer;
292
+ }
293
+
212
294
  // src/universal/core/shared/blockchain/erc20/abi.ts
213
295
  var ERC20_ABI = [
214
296
  {
@@ -541,6 +623,12 @@ function requiredDate(value, field) {
541
623
  }
542
624
  return date;
543
625
  }
626
+ function dateOrNull(value, field) {
627
+ return value === null ? null : requiredDate(value, field);
628
+ }
629
+ function decimalOrNull(value) {
630
+ return value === null ? null : DecimalString(value);
631
+ }
544
632
  function requiredArray(value, field) {
545
633
  if (!Array.isArray(value)) {
546
634
  throw new ValidationError(
@@ -556,7 +644,8 @@ var OfferSlug = (value) => value;
556
644
  var OFFER_TYPES = {
557
645
  "coinlist::token_sale": true,
558
646
  "superstate::swap": true,
559
- "ondo::swap": true
647
+ "ondo::swap": true,
648
+ "centrifuge::vault": true
560
649
  };
561
650
  var isOfferType = (value) => Object.keys(OFFER_TYPES).includes(value);
562
651
  var OfferEditorial = {
@@ -598,6 +687,7 @@ var Offer = {
598
687
  // on the wire: copying them would hand every consumer a `startsAt` that
599
688
  // is 1970 for one variant and a date for the others.
600
689
  case "ondo::swap":
690
+ case "centrifuge::vault":
601
691
  return {
602
692
  ...base2,
603
693
  type: dto.type,
@@ -650,6 +740,19 @@ var OfferSwapContract = {
650
740
  address: EvmContractAddress(dto.address)
651
741
  })
652
742
  };
743
+ var OfferVaultContract = {
744
+ fromDto: (dto) => ({
745
+ chain: Chain(dto.chain),
746
+ address: EvmContractAddress(dto.address)
747
+ }),
748
+ /**
749
+ * Drops every proxy on a chain this SDK does not name, as
750
+ * `OfferToken.listFromDto` does: frontline publishes the full offer-token
751
+ * chain list, which runs ahead of {@link Chain}, and one unmodelled chain
752
+ * should not throw away the proxies the SDK can use.
753
+ */
754
+ listFromDto: (dtos) => dtos.filter((dto) => isChain(dto.chain)).map(OfferVaultContract.fromDto)
755
+ };
653
756
  var OfferDetail = {
654
757
  fromDto: (dto) => {
655
758
  const fundingAssets = requiredArray(
@@ -712,6 +815,18 @@ var OfferDetail = {
712
815
  "OfferDetail.swap_contracts"
713
816
  ).map(OfferSwapContract.fromDto)
714
817
  };
818
+ // No editorial, as for Ondo. `[]` is fine and a missing key is not, for
819
+ // the same reason as `swap_contracts`: this is the spender a user
820
+ // approves.
821
+ case "centrifuge::vault":
822
+ return {
823
+ ...base2,
824
+ type: dto.type,
825
+ logoUrl: notBlankStringOrNull(dto.logo_url),
826
+ vaultContracts: OfferVaultContract.listFromDto(
827
+ requiredArray(dto.vault_contracts, "OfferDetail.vault_contracts")
828
+ )
829
+ };
715
830
  default:
716
831
  return unknownTypeError(dto, "OfferDetail");
717
832
  }
@@ -1069,7 +1184,11 @@ function formatAmount(amount, locale, options) {
1069
1184
  const decimal = formatRawAmount(amount);
1070
1185
  let [integerPart, fractionPart = ""] = decimal.split(".");
1071
1186
  const maxFractionDigits = options?.maxFractionDigits;
1072
- const minFractionDigits = maxFractionDigits !== void 0 ? Math.min(maxFractionDigits, amount.decimals) : Math.min(2, amount.decimals);
1187
+ const minFractionDigits = Math.min(
1188
+ options?.minFractionDigits ?? (maxFractionDigits !== void 0 ? maxFractionDigits : 2),
1189
+ maxFractionDigits ?? amount.decimals,
1190
+ amount.decimals
1191
+ );
1073
1192
  if (maxFractionDigits !== void 0 && fractionPart.length > maxFractionDigits) {
1074
1193
  ({ integerPart, fractionPart } = roundFractionHalfUp(
1075
1194
  integerPart,
@@ -1077,7 +1196,8 @@ function formatAmount(amount, locale, options) {
1077
1196
  maxFractionDigits
1078
1197
  ));
1079
1198
  }
1080
- const fraction = minFractionDigits === 0 ? "" : fractionPart.padEnd(minFractionDigits, "0");
1199
+ const trimmed = fractionPart.replace(/0+$/, "");
1200
+ const fraction = trimmed.padEnd(minFractionDigits, "0");
1081
1201
  const integerFormatted = BigInt(integerPart).toLocaleString(locale);
1082
1202
  if (minFractionDigits === 0 && fraction === "") {
1083
1203
  return FormattedAmountUi(integerFormatted);
@@ -1146,6 +1266,14 @@ function assetAmount(amount, symbol) {
1146
1266
  return FormattedAmountAssetUi(`${amount} ${symbol}`);
1147
1267
  }
1148
1268
  var NA_AMOUNT_ASSET_UI = FormattedAmountAssetUi("-");
1269
+ var SHARE_MAX_FRACTION_DIGITS = AssetDecimals(6);
1270
+ var SHARE_MIN_FRACTION_DIGITS = AssetDecimals(2);
1271
+ function formatShareAmount(amount, locale) {
1272
+ return formatAmount(amount, locale, {
1273
+ maxFractionDigits: SHARE_MAX_FRACTION_DIGITS,
1274
+ minFractionDigits: SHARE_MIN_FRACTION_DIGITS
1275
+ });
1276
+ }
1149
1277
  var USD_FRACTION_DIGITS = AssetDecimals(2);
1150
1278
  function formattedUsdPrice(amount, locale) {
1151
1279
  if (!amount) return NA_AMOUNT_ASSET_UI;
@@ -1287,7 +1415,6 @@ var OndoTradingStatus = {
1287
1415
  };
1288
1416
  }
1289
1417
  };
1290
- var decimalOrNull = (value) => value === null ? null : DecimalString(value);
1291
1418
  var OndoQuote = {
1292
1419
  fromDto: (dto) => {
1293
1420
  const assetDecimals = AssetDecimals(dto.asset_decimals);
@@ -2944,11 +3071,18 @@ var HttpClient = class {
2944
3071
  );
2945
3072
  let response = await this.executeRequest(preparedRequest);
2946
3073
  for (const middleware of this.middleware.afterRequest ?? []) {
3074
+ let retried = false;
2947
3075
  response = await middleware({
2948
3076
  request: preparedRequest,
2949
3077
  response,
2950
- retry: (nextRequest = preparedRequest) => this.runRequestWithAfterMiddleware(nextRequest)
3078
+ retry: (nextRequest = preparedRequest) => {
3079
+ retried = true;
3080
+ return this.runRequestWithAfterMiddleware(nextRequest);
3081
+ }
2951
3082
  });
3083
+ if (retried) {
3084
+ return response;
3085
+ }
2952
3086
  }
2953
3087
  return response;
2954
3088
  }
@@ -3394,6 +3528,9 @@ export {
3394
3528
  VERIFY_IDENTITY_PATH,
3395
3529
  VERIFY_IDENTITY_ACCREDITATION_PATH,
3396
3530
  WALLET_PATH,
3531
+ SentWith,
3532
+ TokenTrigger,
3533
+ isRejectedToken,
3397
3534
  Attributes,
3398
3535
  HttpError,
3399
3536
  apiErrorCode,
@@ -3406,6 +3543,10 @@ export {
3406
3543
  describeErrorUnredacted,
3407
3544
  internalLogger,
3408
3545
  HttpClient,
3546
+ EpochMillis,
3547
+ Millis,
3548
+ Seconds,
3549
+ isExpired,
3409
3550
  ERC20_ABI,
3410
3551
  fetchAllPages,
3411
3552
  Cursor,
@@ -3435,6 +3576,9 @@ export {
3435
3576
  StablecoinSymbol,
3436
3577
  KnownAssetSymbol,
3437
3578
  Bps,
3579
+ requiredDate,
3580
+ dateOrNull,
3581
+ decimalOrNull,
3438
3582
  OfferId,
3439
3583
  OfferSlug,
3440
3584
  isOfferType,
@@ -3446,6 +3590,7 @@ export {
3446
3590
  OfferOptionId,
3447
3591
  OfferOptionSlug,
3448
3592
  OfferSwapContract,
3593
+ OfferVaultContract,
3449
3594
  OfferDetail,
3450
3595
  OfferOption,
3451
3596
  FaqItem,
@@ -3473,6 +3618,7 @@ export {
3473
3618
  usdAmount,
3474
3619
  assetAmount,
3475
3620
  NA_AMOUNT_ASSET_UI,
3621
+ formatShareAmount,
3476
3622
  formattedUsdPrice,
3477
3623
  viemChain,
3478
3624
  getChainId,
@@ -3521,4 +3667,4 @@ export {
3521
3667
  OAuthRefreshToken,
3522
3668
  OAuthSession
3523
3669
  };
3524
- //# sourceMappingURL=chunk-MOE5S6CE.js.map
3670
+ //# sourceMappingURL=chunk-NBO5E2RO.js.map