@coinlist-co/react 0.12.2-rc.726778a → 0.12.2-rc.818bed6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-C6F5FOFP.js → chunk-2HCSM3FB.js} +52 -19
- package/dist/chunk-2HCSM3FB.js.map +1 -0
- package/dist/{chunk-3UQPCAU4.js → chunk-OUFI3D3Q.js} +84 -4
- package/dist/chunk-OUFI3D3Q.js.map +1 -0
- package/dist/{chunk-IRV5YU42.js → chunk-YMDYLYL3.js} +941 -442
- package/dist/chunk-YMDYLYL3.js.map +1 -0
- package/dist/client/index.cjs +12358 -8385
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1965 -227
- package/dist/client/index.d.ts +1965 -227
- package/dist/client/index.js +8766 -5370
- package/dist/client/index.js.map +1 -1
- package/dist/server/index.cjs +842 -142
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +72 -41
- package/dist/server/index.d.ts +72 -41
- package/dist/server/index.js +323 -65
- package/dist/server/index.js.map +1 -1
- package/dist/{tokens-namespace-CIWBtgt2.d.cts → time-DJU6E8KK.d.cts} +943 -192
- package/dist/{tokens-namespace-CIWBtgt2.d.ts → time-DJU6E8KK.d.ts} +943 -192
- package/dist/{token-registry-snapshot-CqI6dXM_.d.cts → token-registry-snapshot-B0xCp3GD.d.cts} +74 -8
- package/dist/{token-registry-snapshot-BNBNuDu1.d.ts → token-registry-snapshot-DGG5N27C.d.ts} +74 -8
- package/dist/universal/index.cjs +1022 -440
- package/dist/universal/index.cjs.map +1 -1
- package/dist/universal/index.d.cts +88 -5
- package/dist/universal/index.d.ts +88 -5
- package/dist/universal/index.js +46 -2
- package/package.json +1 -1
- package/dist/chunk-3UQPCAU4.js.map +0 -1
- package/dist/chunk-C6F5FOFP.js.map +0 -1
- package/dist/chunk-IRV5YU42.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-
|
|
12
|
+
} from "./chunk-YMDYLYL3.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
|
-
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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(
|
|
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-
|
|
438
|
+
//# sourceMappingURL=chunk-2HCSM3FB.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":[]}
|
|
@@ -3,13 +3,16 @@ import {
|
|
|
3
3
|
BlockchainAmount,
|
|
4
4
|
Bps,
|
|
5
5
|
DecimalString,
|
|
6
|
+
ETHEREUM_CHAINS,
|
|
6
7
|
EvmContractAddress,
|
|
7
8
|
InvariantError,
|
|
8
9
|
MAX_UINT_256,
|
|
9
10
|
MathError,
|
|
10
11
|
NA_AMOUNT_ASSET_UI,
|
|
12
|
+
STABLE_DECIMALS,
|
|
11
13
|
SUPERSTATE_SWAP_ABI,
|
|
12
14
|
StablecoinSymbol,
|
|
15
|
+
apiErrorCode,
|
|
13
16
|
arrayBufferToBase64Url,
|
|
14
17
|
assertUint256,
|
|
15
18
|
fetchOfferDetails,
|
|
@@ -19,7 +22,7 @@ import {
|
|
|
19
22
|
generateSecureRandomBase64Url,
|
|
20
23
|
internalLogger,
|
|
21
24
|
sha256
|
|
22
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-YMDYLYL3.js";
|
|
23
26
|
|
|
24
27
|
// src/universal/types/oauth.ts
|
|
25
28
|
var AuthorizationCode = (value) => value;
|
|
@@ -47,6 +50,48 @@ async function generatePKCEParams(config) {
|
|
|
47
50
|
};
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
// src/universal/core/checkout/centrifuge/constants.ts
|
|
54
|
+
var CENTRIFUGE_POLL_INTERVAL_MS = 15e3;
|
|
55
|
+
var CENTRIFUGE_INDICATIVE_QUOTE_AMOUNT = BlockchainAmount({
|
|
56
|
+
raw: 100000000n,
|
|
57
|
+
decimals: STABLE_DECIMALS
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// src/universal/core/checkout/centrifuge/vaults.ts
|
|
61
|
+
function resolveCentrifugeVaults(offer, lookup) {
|
|
62
|
+
return offer.vaultContracts.map((contract) => {
|
|
63
|
+
const { chain } = contract;
|
|
64
|
+
if (!isEthereumChain(chain)) {
|
|
65
|
+
return { type: "unresolved", chain, missing: "evm-chain" };
|
|
66
|
+
}
|
|
67
|
+
const onChain = (role) => offer.tokens.filter(
|
|
68
|
+
(token) => token.role === role && token.chain === chain
|
|
69
|
+
);
|
|
70
|
+
const share = firstKnown(onChain("vault"), lookup);
|
|
71
|
+
if (share === null) {
|
|
72
|
+
return { type: "unresolved", chain, missing: "share-token" };
|
|
73
|
+
}
|
|
74
|
+
const funding = firstKnown(onChain("funding"), lookup);
|
|
75
|
+
if (funding === null) {
|
|
76
|
+
return { type: "unresolved", chain, missing: "funding-token" };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
type: "resolved",
|
|
80
|
+
vault: { chain, proxy: contract.address, share, funding }
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function firstKnown(tokens, lookup) {
|
|
85
|
+
for (const token of tokens) {
|
|
86
|
+
const metadata = lookup(token);
|
|
87
|
+
if (metadata !== null) return metadata;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function isEthereumChain(chain) {
|
|
92
|
+
return chain in ETHEREUM_CHAINS;
|
|
93
|
+
}
|
|
94
|
+
|
|
50
95
|
// src/universal/core/shared/blockchain/token-registry.ts
|
|
51
96
|
var USDC_SYMBOL = StablecoinSymbol("USDC");
|
|
52
97
|
var USDC = {
|
|
@@ -163,6 +208,12 @@ function assertPriceFits(raw) {
|
|
|
163
208
|
}
|
|
164
209
|
}
|
|
165
210
|
|
|
211
|
+
// src/universal/core/checkout/ondo/not-tradable.ts
|
|
212
|
+
var ONDO_NOT_TRADABLE_CODE = "not_tradable";
|
|
213
|
+
function isOndoNotTradableError(error) {
|
|
214
|
+
return apiErrorCode(error) === ONDO_NOT_TRADABLE_CODE;
|
|
215
|
+
}
|
|
216
|
+
|
|
166
217
|
// src/universal/core/shared/blockchain/bps.ts
|
|
167
218
|
var BPS_DENOM = Bps(10000n);
|
|
168
219
|
function applyBps(amount, bps) {
|
|
@@ -332,7 +383,9 @@ var TokenRegistrySnapshot = {
|
|
|
332
383
|
}
|
|
333
384
|
};
|
|
334
385
|
function offerDisplayTokens(offer) {
|
|
335
|
-
const swaps = offer.tokens.filter(
|
|
386
|
+
const swaps = offer.tokens.filter(
|
|
387
|
+
(token) => token.role === "swap" || token.role === "vault"
|
|
388
|
+
);
|
|
336
389
|
const distributions = offer.tokens.filter(
|
|
337
390
|
(token) => token.role === "distribution"
|
|
338
391
|
);
|
|
@@ -347,6 +400,27 @@ var RedactedWalletError = {
|
|
|
347
400
|
fromWalletError: (error) => error.type === "unknown" ? { type: "unknown" } : error
|
|
348
401
|
};
|
|
349
402
|
|
|
403
|
+
// src/universal/types/blockchain/wallet-selection.ts
|
|
404
|
+
var WalletSelection = {
|
|
405
|
+
/**
|
|
406
|
+
* Every wallet in display order - embedded first, then the external one -
|
|
407
|
+
* each address once. Addresses compare case-insensitively, so a host that
|
|
408
|
+
* hands the same wallet over as both embedded and external sees it once.
|
|
409
|
+
*/
|
|
410
|
+
all: (selection) => {
|
|
411
|
+
const seen = /* @__PURE__ */ new Set();
|
|
412
|
+
return [
|
|
413
|
+
...selection.embedded,
|
|
414
|
+
...selection.external === null ? [] : [selection.external]
|
|
415
|
+
].filter((wallet) => {
|
|
416
|
+
const key = wallet.address.toLowerCase();
|
|
417
|
+
if (seen.has(key)) return false;
|
|
418
|
+
seen.add(key);
|
|
419
|
+
return true;
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
350
424
|
// src/universal/types/user.ts
|
|
351
425
|
var UserId = (value) => value;
|
|
352
426
|
var UserEmail = (value) => value;
|
|
@@ -366,6 +440,10 @@ export {
|
|
|
366
440
|
ClientId,
|
|
367
441
|
ClientSecret,
|
|
368
442
|
generatePKCEParams,
|
|
443
|
+
RedactedWalletError,
|
|
444
|
+
CENTRIFUGE_POLL_INTERVAL_MS,
|
|
445
|
+
CENTRIFUGE_INDICATIVE_QUOTE_AMOUNT,
|
|
446
|
+
resolveCentrifugeVaults,
|
|
369
447
|
USDC_SYMBOL,
|
|
370
448
|
USDC,
|
|
371
449
|
USDT_SYMBOL,
|
|
@@ -380,6 +458,8 @@ export {
|
|
|
380
458
|
computeOndoBuyPrice,
|
|
381
459
|
computeOndoSellPrice,
|
|
382
460
|
ondoQuoteOfferDeadline,
|
|
461
|
+
ONDO_NOT_TRADABLE_CODE,
|
|
462
|
+
isOndoNotTradableError,
|
|
383
463
|
BPS_DENOM,
|
|
384
464
|
applyBps,
|
|
385
465
|
computePrice,
|
|
@@ -401,9 +481,9 @@ export {
|
|
|
401
481
|
swapSpender,
|
|
402
482
|
TokenRegistrySnapshot,
|
|
403
483
|
offerDisplayTokens,
|
|
404
|
-
|
|
484
|
+
WalletSelection,
|
|
405
485
|
UserId,
|
|
406
486
|
UserEmail,
|
|
407
487
|
User
|
|
408
488
|
};
|
|
409
|
-
//# sourceMappingURL=chunk-
|
|
489
|
+
//# sourceMappingURL=chunk-OUFI3D3Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/universal/types/oauth.ts","../src/universal/core/auth/pkce.ts","../src/universal/core/checkout/centrifuge/constants.ts","../src/universal/core/checkout/centrifuge/vaults.ts","../src/universal/core/shared/blockchain/token-registry.ts","../src/universal/core/checkout/ondo/constants.ts","../src/universal/core/checkout/ondo/math.ts","../src/universal/core/checkout/ondo/not-tradable.ts","../src/universal/core/shared/blockchain/bps.ts","../src/universal/core/checkout/superstate/blockchain/math.ts","../src/universal/core/checkout/superstate/blockchain/quote.ts","../src/universal/core/checkout/superstate/blockchain/status.ts","../src/universal/core/checkout/superstate/blockchain/swapped-event.ts","../src/universal/core/checkout/superstate/constants.ts","../src/universal/core/checkout/superstate/formatters.ts","../src/universal/core/offers/offers-namespace.ts","../src/universal/core/offers/swap-contracts.ts","../src/universal/core/tokens/token-registry-snapshot.ts","../src/universal/types/blockchain/wallet-error.ts","../src/universal/types/blockchain/wallet-selection.ts","../src/universal/types/user.ts"],"sourcesContent":["import type { Newtype } from '@/universal/types/newtype';\n\nexport type AuthorizationCode = Newtype<string, 'AuthorizationCode'>;\nexport const AuthorizationCode = (value: string): AuthorizationCode =>\n value as AuthorizationCode;\n\nexport type CodeVerifier = Newtype<string, 'CodeVerifier'>;\nexport const CodeVerifier = (value: string): CodeVerifier =>\n value as CodeVerifier;\n\nexport type CodeChallenge = Newtype<string, 'CodeChallenge'>;\nexport const CodeChallenge = (value: string): CodeChallenge =>\n value as CodeChallenge;\n\nexport type PKCEState = Newtype<string, 'PKCEState'>;\nexport const PKCEState = (value: string) => value as PKCEState;\n\nexport type RedirectUri = Newtype<string, 'RedirectUri'>;\nexport const RedirectUri = (value: string): RedirectUri => value as RedirectUri;\n\nexport type ClientId = Newtype<string, 'ClientId'>;\nexport const ClientId = (value: string): ClientId => value as ClientId;\n\nexport type ClientSecret = Newtype<string, 'ClientSecret'>;\nexport const ClientSecret = (value: string): ClientSecret =>\n value as ClientSecret;\n","import {\n arrayBufferToBase64Url,\n generateSecureRandomBase64Url,\n sha256,\n} from '@/universal/core/shared/utils/crypto';\nimport type {\n CodeChallenge as CodeChallengeType,\n CodeVerifier as CodeVerifierType,\n PKCEState as PKCEStateType,\n} from '@/universal/types/oauth';\nimport {\n type ClientId,\n CodeChallenge,\n CodeVerifier,\n PKCEState,\n type RedirectUri,\n} from '@/universal/types/oauth';\n\nexport type PKCEParams = {\n clientId: ClientId;\n responseType: 'code';\n redirectUri: RedirectUri;\n codeChallenge: CodeChallengeType;\n codeChallengeMethod: 'S256';\n state: PKCEStateType;\n codeVerifier: CodeVerifierType;\n};\n\nexport type PKCEConfig = {\n clientId: ClientId;\n redirectUri: RedirectUri;\n};\n\n/**\n * Generates PKCE parameters for an OAuth2 authorization code flow.\n * Pure crypto — no React, no DOM beyond Web Crypto API. Safe to use\n * in server-side code and Next.js middleware.\n */\nexport async function generatePKCEParams(\n config: PKCEConfig\n): Promise<PKCEParams> {\n const state = generateSecureRandomBase64Url(32);\n const codeVerifier = generateSecureRandomBase64Url(32);\n const codeChallengeRaw = await sha256(codeVerifier);\n const codeChallenge = arrayBufferToBase64Url(codeChallengeRaw, false);\n\n return {\n clientId: config.clientId,\n responseType: 'code',\n redirectUri: config.redirectUri,\n codeChallenge: CodeChallenge(codeChallenge),\n codeChallengeMethod: 'S256',\n state: PKCEState(state),\n codeVerifier: CodeVerifier(codeVerifier),\n };\n}\n","import {\n BlockchainAmount,\n STABLE_DECIMALS,\n} from '@/universal/types/blockchain/core';\n\n/**\n * How often the Centrifuge checkout refreshes what it is allowed to poll: the\n * vault status and the indicative quote. Both are free - neither spends an\n * attestation - so this is paced for a gate the user can trust rather than\n * for cost.\n *\n * There is no quote TTL beside this. A Centrifuge quote does not expire: it\n * is indicative, the fund prices fulfilment later, and a countdown would\n * imply a commitment the API does not make.\n */\nexport const CENTRIFUGE_POLL_INTERVAL_MS = 15_000;\n\n/**\n * The spend the asset panel prices while the user has not confirmed an amount:\n * 100 USDC.\n *\n * A fixed size so typing in the amount field does not restart this poll. The\n * review step polls a quote for the confirmed spend, which is the estimate it\n * shows.\n */\nexport const CENTRIFUGE_INDICATIVE_QUOTE_AMOUNT = BlockchainAmount({\n raw: 100_000_000n,\n decimals: STABLE_DECIMALS,\n});\n","import {\n ETHEREUM_CHAINS,\n type EthereumChain,\n type EvmContractAddress,\n} from '@/universal/types/blockchain/core';\nimport type { OfferToken } from '@/universal/types/offer';\nimport type { CentrifugeVaultOfferDetail } from '@/universal/types/offer-detail';\nimport type { TokenMetadata } from '@/universal/types/token-metadata';\n\n/**\n * One chain a vault offer runs on, with everything a screen needs to read and\n * show a position there: the proxy every request and claim goes through, the\n * share token it issues, and the funding token it is paid in.\n *\n * The share token's `symbol` is what every `/v1/centrifuge/vault/*` call is\n * keyed by.\n */\nexport type CentrifugeVault = {\n chain: EthereumChain;\n proxy: EvmContractAddress;\n share: TokenMetadata;\n funding: TokenMetadata;\n};\n\n/**\n * The vaults an offer runs on, or why a chain could not be resolved: one\n * entry per `vaultContracts` chain, in the offer's order.\n */\nexport type CentrifugeVaultResolution =\n | { type: 'resolved'; vault: CentrifugeVault }\n | {\n type: 'unresolved';\n chain: OfferToken['chain'];\n /** Which token the offer or the registry could not name. */\n missing: 'share-token' | 'funding-token' | 'evm-chain';\n };\n\n/**\n * Resolves each chain of a vault offer to its share and funding tokens.\n *\n * The offer names tokens by role and address only - the share token as\n * `vault`, the coin it is paid in as `funding` - so `lookup` turns each into\n * metadata. A function rather than a registry snapshot, so this module does\n * not depend on where the metadata comes from.\n *\n * A chain whose tokens cannot be named is reported rather than dropped: its\n * position cannot be read without the symbol, and a caller has to know it is\n * not showing it. When a chain lists several funding tokens, the first one\n * `lookup` knows wins - a vault is priced in one.\n */\nexport function resolveCentrifugeVaults(\n offer: CentrifugeVaultOfferDetail,\n lookup: (token: OfferToken) => TokenMetadata | null\n): CentrifugeVaultResolution[] {\n return offer.vaultContracts.map((contract) => {\n const { chain } = contract;\n if (!isEthereumChain(chain)) {\n return { type: 'unresolved', chain, missing: 'evm-chain' };\n }\n const onChain = (role: OfferToken['role']) =>\n offer.tokens.filter(\n (token) => token.role === role && token.chain === chain\n );\n const share = firstKnown(onChain('vault'), lookup);\n if (share === null) {\n return { type: 'unresolved', chain, missing: 'share-token' };\n }\n const funding = firstKnown(onChain('funding'), lookup);\n if (funding === null) {\n return { type: 'unresolved', chain, missing: 'funding-token' };\n }\n return {\n type: 'resolved',\n vault: { chain, proxy: contract.address, share, funding },\n };\n });\n}\n\nfunction firstKnown(\n tokens: OfferToken[],\n lookup: (token: OfferToken) => TokenMetadata | null\n): TokenMetadata | null {\n for (const token of tokens) {\n const metadata = lookup(token);\n if (metadata !== null) return metadata;\n }\n return null;\n}\n\nfunction isEthereumChain(chain: OfferToken['chain']): chain is EthereumChain {\n return chain in ETHEREUM_CHAINS;\n}\n","import {\n AssetDecimals,\n type Erc20Asset,\n type EthereumChain,\n EvmContractAddress,\n type KnownAssetSymbol,\n StablecoinSymbol,\n} from '@/universal/types/blockchain/core';\nimport { InvariantError } from '@/universal/types/errors';\n\nexport const USDC_SYMBOL: StablecoinSymbol = StablecoinSymbol('USDC');\nexport const USDC: Erc20Asset = {\n name: 'USD Coin',\n symbol: USDC_SYMBOL,\n decimals: AssetDecimals(6),\n};\n\nexport const USDT_SYMBOL: StablecoinSymbol = StablecoinSymbol('USDT');\nexport const USDT: Erc20Asset = {\n name: 'Tether USD',\n symbol: USDT_SYMBOL,\n decimals: AssetDecimals(6),\n};\n\n/** ERC-20 contract addresses for a supported stablecoin, keyed by chain. */\ntype TokenAddresses = Record<EthereumChain, EvmContractAddress>;\n\nconst USDC_ADDRESSES: TokenAddresses = {\n ethereum_mainnet: EvmContractAddress(\n '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'\n ),\n ethereum_sepolia: EvmContractAddress(\n '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'\n ),\n base_mainnet: EvmContractAddress(\n '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'\n ),\n base_sepolia: EvmContractAddress(\n '0x036CbD53842c5426634e7929541eC2318f3dCF7e'\n ),\n};\n\n/**\n * USDT is not deployed on every chain the SDK models: Tether publishes no\n * Base Sepolia token. The table stays total, with that one absence an\n * explicit `null`, so a new {@link EthereumChain} is still a compile error\n * here until the author decides whether USDT exists on it — the same\n * exhaustiveness the rest of the chain model relies on. A lookup then\n * answers the honest `null` rather than calling a fake address.\n */\nconst USDT_ADDRESSES: Record<EthereumChain, EvmContractAddress | null> = {\n ethereum_mainnet: EvmContractAddress(\n '0xdac17f958d2ee523a2206206994597c13d831ec7'\n ),\n ethereum_sepolia: EvmContractAddress(\n '0x7169D38820dfd117C3FA1f22a697dBA58d90BA06'\n ),\n base_mainnet: EvmContractAddress(\n '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2'\n ),\n base_sepolia: null,\n};\n\n/** Lookups for the stablecoins the SDK's swap flows support out of the box. */\nexport const TOKEN_REGISTRY = {\n erc20: (symbol: KnownAssetSymbol): Erc20Asset => {\n switch (symbol) {\n case 'USDC':\n return USDC;\n case 'USDT':\n return USDT;\n default:\n throw new InvariantError(`Unknown asset symbol: ${symbol}`);\n }\n },\n /**\n * USDC's contract on `chain`. USDC is deployed on every chain the SDK\n * models, so the table is total and the answer is never `null`.\n */\n usdcAddress: (chain: EthereumChain): EvmContractAddress =>\n USDC_ADDRESSES[chain],\n /**\n * USDT's contract on `chain`, or `null` where Tether publishes none —\n * Base Sepolia today. The honest answer, never a fake address a balance\n * read would call.\n */\n usdtAddress: (chain: EthereumChain): EvmContractAddress | null =>\n USDT_ADDRESSES[chain],\n};\n","import { USDC_SYMBOL } from '@/universal/core/shared/blockchain/token-registry';\nimport {\n DecimalString,\n type StablecoinSymbol,\n} from '@/universal/types/blockchain/core';\nimport type { NonEmptyArray } from '@/universal/types/collections';\n\n/**\n * How often the Ondo checkout refreshes what it is allowed to poll: the market\n * status and the indicative price. Both are free - neither spends an\n * attestation - so this is paced for a price the user can trust rather than\n * for cost.\n *\n * A built transaction is deliberately not on this timer. Building spends an\n * attestation, so it happens once per order and once per refresh the user\n * asks for.\n */\nexport const ONDO_POLL_INTERVAL_MS = 15_000;\n\n/** Max time the review step offers a built quote. */\nexport const ONDO_QUOTE_MAX_TTL_MS = 15_000;\n\n/**\n * The order size the sidebar prices against before the user names one.\n *\n * Ondo prices by size, so some amount has to be sent. $1,000 is a plausible\n * order rather than a token one: a size so small that Ondo's minimum rejects\n * it would leave the panel permanently blank.\n *\n * Named distinctly from `DEFAULT_AMOUNT_TO_COMPUTE_PRICE` in the swap\n * constants, which is a raw `bigint` of USDC base units for a different\n * endpoint. Both barrels are flat, so two constants of the same name could not\n * coexist - and they mean different things anyway.\n */\nexport const DEFAULT_ONDO_AMOUNT_TO_COMPUTE_PRICE = DecimalString('1000');\n\n/**\n * The coin an Ondo sell settles into, for display only.\n *\n * The scale of every amount on a built transaction comes off the response, so\n * this names the token and never sizes it - `receive_output_decimals` is the\n * only authority on how many decimals the proceeds are counted in, and the two\n * are allowed to disagree.\n *\n * Named apart from {@link ONDO_SUPPORTED_INPUT_ASSETS} rather than read out of\n * it, though both are USDC today. On a buy USDC is what the user funds with\n * and one of a list that may grow; on a sell it is what they settle into and\n * there is one of it. Reading `ONDO_SUPPORTED_INPUT_ASSETS[0]` here would make\n * the sell screen quietly follow whichever funding coin happened to be listed\n * first.\n */\nexport const ONDO_SETTLEMENT_ASSET: StablecoinSymbol = USDC_SYMBOL;\n\n/**\n * The order size the sidebar prices a sell against before the user names one.\n *\n * Sells are quoted by token amount rather than by notional: the fee comes out\n * of dollars, and Ondo's price is size-aware, so a buy's quantity depends on\n * the quote being asked for while a sell is sized by what the user already\n * holds. One whole share is a size Ondo will price - small enough to be\n * plausible for any asset, large enough that Ondo's minimum does not reject it\n * and leave the panel permanently blank.\n *\n * The buy counterpart is {@link DEFAULT_ONDO_AMOUNT_TO_COMPUTE_PRICE}, which\n * is a dollar figure for the same reason in reverse.\n */\nexport const DEFAULT_ONDO_SELL_TOKENS_TO_COMPUTE_PRICE = 1;\n\n/**\n * The coins an Ondo buy can be funded with, in display order, with the first\n * preselected.\n *\n * USDC alone today: it is what the Sepolia integration contract accepts. The\n * amount step renders a single entry as a plain card and two or more as a\n * picker, so adding one here is all it takes.\n */\nexport const ONDO_SUPPORTED_INPUT_ASSETS: NonEmptyArray<StablecoinSymbol> = [\n USDC_SYMBOL,\n];\n","import { ONDO_QUOTE_MAX_TTL_MS } from '@/universal/core/checkout/ondo/constants';\nimport {\n assertUint256,\n BlockchainAmount,\n MAX_UINT_256,\n type Uint256,\n} from '@/universal/types/blockchain/core';\nimport { MathError } from '@/universal/types/errors';\nimport type {\n OndoBuyTransaction,\n OndoSellOutcome,\n OndoSellTransaction,\n} from '@/universal/types/providers/ondo/ondo';\n\n/**\n * The price of one whole asset token in a built Ondo purchase, in the funding\n * token's units - USD per share.\n *\n * Neither builder publishes a price. Frontline drops Ninshubur's deliberately,\n * because `GET /v1/ondo/swap/quote` already serves one under that name at a\n * different scale, and echoing both would leave a caller to guess which was\n * which.\n *\n * Dividing the two amounts the response *does* carry is better than reading\n * the indicative one anyway: it is the price this transaction fills at, rather\n * than a poll that has since moved and was struck against a different size.\n *\n * **Fee-exclusive.** `notionalValue` is the deposit after CoinList's fee comes\n * off, so this is the price Ondo filled at, not the buyer's all-in cost per\n * token - that would be `spendInputAmount / receiveOutputAmount`, and it is\n * higher. Fee-exclusive is what the review screen wants, because it renders\n * the fee on its own line: price x quantity, plus fee, comes to the total.\n * Making this all-in would double-count the fee against that breakdown.\n *\n * The two are equal until ENG-1718 turns a fee on, which frontline rejects\n * today. They diverge the moment it does, so the choice is load-bearing rather\n * than academic.\n */\nexport function computeOndoBuyPrice(\n transaction: OndoBuyTransaction\n): BlockchainAmount {\n const { notionalValue, receiveOutputAmount } = transaction;\n return pricePerShare({\n usd: notionalValue,\n shares: receiveOutputAmount,\n });\n}\n\n/**\n * The price of one whole asset token in a built Ondo sale, in the settlement\n * token's units - USD per share, the same thing\n * {@link computeOndoBuyPrice} answers with the ratio inverted.\n *\n * **Fee-exclusive, and it takes arithmetic to be so.** `expected.quantity` is\n * published already net of `expected.fee`, so the proceeds are added back\n * before dividing: what comes out is the price Ondo filled at, which is the\n * buy arm's meaning rather than a second one. That is what lets one review row\n * render on either screen, and what keeps the sell breakdown honest - the fee\n * sits on its own line beside this, and a net price would count it twice.\n *\n * Read against {@link OndoSellTransaction.expected} alone. The `minimum` arm\n * describes the same trade at a different outcome, and pricing that one would\n * answer \"the worst price this could fill at\", which no screen asks for.\n *\n * The two readings coincide while frontline forces the fee to zero, which it\n * does today. They diverge the day ENG-1718 lands.\n */\nexport function computeOndoSellPrice(\n transaction: OndoSellTransaction\n): BlockchainAmount {\n const { expected, spendInputAmount } = transaction;\n return pricePerShare({\n usd: grossProceeds(expected),\n shares: spendInputAmount,\n });\n}\n\n/** The sooner of Ondo's deadline and now + {@link ONDO_QUOTE_MAX_TTL_MS}. */\nexport function ondoQuoteOfferDeadline(\n expiresAt: Date,\n nowMs: number = Date.now()\n): Date {\n return new Date(Math.min(expiresAt.getTime(), nowMs + ONDO_QUOTE_MAX_TTL_MS));\n}\n\n/**\n * What Ondo returned before CoinList took its cut: the published quantity with\n * the fee charged on it added back.\n *\n * Bounded before the addition rather than after. The two amounts are parsed\n * independently, each a uint256 in its own right, so nothing in the mapper\n * stops their sum leaving the range - and `BlockchainAmount.add` answers an\n * out-of-range sum with an `InvariantError`, which says the SDK's own\n * arithmetic went wrong. It did not: a gross that no uint256 can hold is a\n * corrupt response, the same kind of thing {@link assertPriceFits} reports, and\n * it has to say so here rather than take the checkout down mid-render.\n *\n * The addition itself is still `BlockchainAmount.add`, so the two amounts\n * disagreeing about their scale stays an `InvariantError`. That one really is\n * internal: `OndoSellTransaction.fromDto` reads both off\n * `receive_output_decimals`.\n */\nfunction grossProceeds(expected: OndoSellOutcome): BlockchainAmount {\n const raw = expected.quantity.raw + expected.fee.raw;\n if (raw > MAX_UINT_256) {\n throw new MathError(`Ondo sell proceeds: out of uint256 bounds (${raw})`);\n }\n return BlockchainAmount.add(expected.quantity, expected.fee);\n}\n\n/**\n * USD per whole share, in the USD amount's decimals.\n *\n * Both prices bottom out here so the scaling, the rounding and the bound are\n * written once: whichever side the amounts came off, a price is one dollar\n * amount divided by one asset amount.\n *\n * {@link BlockchainAmount.div} owns the arithmetic - it scales the numerator\n * by the asset's exponent so the quotient lands back in the funding token's,\n * truncates towards zero so the total a price implies never overstates what\n * was paid, and refuses a zero share quantity rather than dividing by it.\n * That last guard is unreachable through either `fromDto`, which rejects a\n * non-positive amount on both sides of the trade at the boundary - both of\n * them, because which one lands in the denominator here flips with the\n * direction. It matters because this runs during render, where a bare BigInt\n * division would take the checkout down with a RangeError instead of failing\n * one step.\n *\n * What is Ondo's rather than every amount's is the uint256 bound: `div`\n * deliberately does not check one, because scaling can carry a legal pair out\n * of range, and here that can only mean a corrupt response.\n */\nfunction pricePerShare({\n usd,\n shares,\n}: {\n usd: BlockchainAmount;\n shares: BlockchainAmount;\n}): BlockchainAmount {\n const price = BlockchainAmount.div(usd, shares);\n return BlockchainAmount({\n raw: assertPriceFits(price.raw),\n decimals: price.decimals,\n });\n}\n\n/**\n * A price past uint256 needs an output amount of a few base units against a\n * notional no real balance holds. It cannot come off the wire, so it is a\n * corrupt response rather than an expensive order.\n */\nfunction assertPriceFits(raw: bigint): Uint256 {\n try {\n return assertUint256(raw);\n } catch {\n throw new MathError(`Ondo price: out of uint256 bounds (${raw})`);\n }\n}\n","import { apiErrorCode, type HttpError } from '@/universal/api/http';\n\n/**\n * The CoinList API's error code for an Ondo quote refused because the asset\n * cannot be traded right now: the market is closed or paused, or the asset is\n * paused or closed for the session. It arrives on a 409.\n *\n * One code for all four of Ondo's reasons, because the remedy is the same:\n * wait for the market to open. Ondo pauses for a moment at every US session\n * change (04:00 and 16:00 ET), so a poll can land on one even while\n * `getTradingStatus` reported the asset tradable a second earlier.\n */\nexport const ONDO_NOT_TRADABLE_CODE = 'not_tradable';\n\n/**\n * Whether `error` is the CoinList API refusing an Ondo quote because the\n * market is not open, as opposed to a request that failed.\n *\n * Total: anything that is not an `HttpError` carrying\n * {@link ONDO_NOT_TRADABLE_CODE} is `false`.\n */\nexport function isOndoNotTradableError(error: unknown): error is HttpError {\n return apiErrorCode(error) === ONDO_NOT_TRADABLE_CODE;\n}\n","import {\n assertUint256,\n Bps,\n type Uint256,\n} from '@/universal/types/blockchain/core';\n\n/** Basis-point denominator: 10_000 bps = 100%. */\nexport const BPS_DENOM: Bps = Bps(10_000n);\n\n/**\n * Applies a basis-point fraction to an amount, flooring the result.\n * `floor((amount × bps) / BPS_DENOM)`. The result is bounds-checked, so an\n * overflow (bps far above BPS_DENOM) or a negative product is rejected.\n */\nexport function applyBps(amount: Uint256, bps: Bps): Uint256 {\n return assertUint256((amount * bps) / BPS_DENOM);\n}\n","import type { SwapQuote } from '@/universal/core/checkout/superstate/blockchain/quote';\nimport { applyBps, BPS_DENOM } from '@/universal/core/shared/blockchain/bps';\nimport {\n assertUint256,\n BlockchainAmount,\n Bps,\n} from '@/universal/types/blockchain/core';\n\n/**\n * Price of 1 output token in USD, assuming the input token is a $1 stablecoin.\n *\n * `raw_price = inputTokenAmount.raw × 10^outputDecimals ÷ outputTokenAmount.raw`\n *\n * The multiplication happens before the division so we never lose precision to\n * integer truncation early. This mirrors standard fixed-point practice on-chain.\n * Returned decimals match the input token decimals (always the stablecoin's 6).\n */\nexport function computePrice({\n inputTokenAmount,\n outputTokenAmount,\n}: SwapQuote): BlockchainAmount | null {\n if (outputTokenAmount.raw === 0n) return null;\n\n const scale = 10n ** BigInt(outputTokenAmount.decimals);\n const raw = assertUint256(\n (inputTokenAmount.raw * scale) / outputTokenAmount.raw\n );\n\n return BlockchainAmount({ raw, decimals: inputTokenAmount.decimals });\n}\n\n/**\n * Applies slippage to an amount, returning the minimum acceptable amount:\n * `amount × (BPS_DENOM − slippage) / BPS_DENOM`. Throws when `slippage` exceeds\n * `BPS_DENOM` (the retained fraction would be negative).\n */\nexport function computeSlip(\n amount: BlockchainAmount,\n slippage: Bps\n): BlockchainAmount {\n const retained = Bps(assertUint256(BPS_DENOM - slippage));\n const slipRaw = applyBps(amount.raw, retained);\n return BlockchainAmount({ raw: slipRaw, decimals: amount.decimals });\n}\n","import {\n type AssetDecimals,\n BlockchainAmount,\n} from '@/universal/types/blockchain/core';\nimport type { SwapPreview } from '@/universal/types/providers/superstate/swap';\n\n/**\n * A read-only quote for a swap as domain amounts: how much goes in, the\n * protocol fee, and how much would come out. Derived from the raw\n * {@link SwapPreview} the swap contract returns.\n */\nexport type SwapQuote = {\n inputTokenAmount: BlockchainAmount;\n fee: BlockchainAmount;\n outputTokenAmount: BlockchainAmount;\n};\n\nexport const SwapQuote = {\n /**\n * Assembles a {@link SwapQuote} from a raw contract {@link SwapPreview}. The\n * input and fee are denominated in the input token; the output in the output\n * token.\n */\n fromPreview: (\n preview: SwapPreview,\n inputDecimals: AssetDecimals,\n outputDecimals: AssetDecimals\n ): SwapQuote => ({\n inputTokenAmount: BlockchainAmount({\n raw: preview.inputAmount,\n decimals: inputDecimals,\n }),\n fee: BlockchainAmount({ raw: preview.fee, decimals: inputDecimals }),\n outputTokenAmount: BlockchainAmount({\n raw: preview.outputAmount,\n decimals: outputDecimals,\n }),\n }),\n};\n","import type { SwapStatus } from '@/universal/types/providers/superstate/swap';\n\n/**\n * The bit `Operable.paused` sets for the swap operation, mirroring the\n * contract's `SWAP_LEVEL` constant.\n *\n * A constant rather than a `SWAP_LEVEL()` read: it is `constant` in the\n * contract, so reading it would spend a round-trip to be told the same number\n * every time.\n */\nexport const SWAP_LEVEL = 2;\n\n/**\n * Whether the contract will actually accept a swap right now.\n *\n * Not the same question as \"is it active\". `paused` is a bitmask over\n * *operations*, so a contract can suspend something unrelated and go on\n * swapping; only the {@link SWAP_LEVEL} bit stops it. Reading `state ===\n * 'active'` instead would refuse swaps the contract is perfectly willing to\n * make.\n */\nexport function isSwapAvailable(status: SwapStatus): boolean {\n switch (status.state) {\n case 'active':\n return true;\n case 'stopped':\n return false;\n case 'paused':\n return (status.pausedLevels & SWAP_LEVEL) === 0;\n default: {\n const _exhaustive: never = status;\n return _exhaustive;\n }\n }\n}\n","import { parseEventLogs, type TransactionReceipt } from 'viem';\nimport { SUPERSTATE_SWAP_ABI } from '@/universal/core/checkout/superstate/blockchain/abi';\nimport {\n type AssetDecimals,\n BlockchainAmount,\n MAX_UINT_256,\n} from '@/universal/types/blockchain/core';\n\n/**\n * Decodes the confirmed output amount from the `Swapped` event in a swap\n * transaction receipt.\n *\n * Returns `null` when the event is absent or the amount is not a valid uint256\n * so callers can fall back to the quote's expected output — the swap already\n * confirmed on-chain, so a missing/undecodable event should not fail the flow.\n */\nexport function decodeSwappedOutputAmount(\n receipt: TransactionReceipt,\n outputDecimals: AssetDecimals\n): BlockchainAmount | null {\n try {\n const [swapped] = parseEventLogs({\n abi: SUPERSTATE_SWAP_ABI,\n eventName: 'Swapped',\n logs: receipt.logs,\n });\n if (!swapped) return null;\n\n const raw = swapped.args.outputAmount;\n if (raw < 0n || raw > MAX_UINT_256) return null;\n return BlockchainAmount({ raw, decimals: outputDecimals });\n } catch {\n // Undecodable log (ABI mismatch / malformed) — fall back to the\n // quote. The swap already confirmed on-chain, so never fail here.\n return null;\n }\n}\n","import { USDC_SYMBOL } from '@/universal/core/shared/blockchain/token-registry';\nimport {\n Bps,\n type EthereumChain,\n EvmContractAddress,\n type StablecoinSymbol,\n} from '@/universal/types/blockchain/core';\nimport type { NonEmptyArray } from '@/universal/types/collections';\n\n/**\n * Who issues the asset a Superstate swap pays out.\n *\n * A constant rather than a field off the offer: no frontline endpoint carries\n * an issuer, and every offer this checkout serves is Superstate's by\n * definition — `CheckoutContainer` routes here on `superstate::swap` alone.\n */\nexport const SUPERSTATE_ISSUER_NAME = 'Superstate';\n\n/**\n * The coins a Superstate swap can be funded with, in display order, with the\n * first preselected.\n *\n * USDC alone today: it is what the deployed swap contract accepts. The amount\n * step renders a single entry as a plain card and two or more as a picker, so\n * adding one here is all it takes.\n */\nexport const SUPERSTATE_SUPPORTED_INPUT_ASSETS: NonEmptyArray<StablecoinSymbol> =\n [USDC_SYMBOL];\n\n/** Poll interval for refreshing on-chain swap quotes and balances. */\nexport const SWAP_POLL_INTERVAL_MS = 15_000;\n\n/**\n * Notional input used to derive a display price-per-share when the user has\n * not yet entered an amount (1 USDC at 6 decimals).\n */\nexport const DEFAULT_AMOUNT_TO_COMPUTE_PRICE = 1_000_000n;\n\n/** Slippage tolerances offered in the review step, in basis points. */\nexport const SLIPPAGE_OPTIONS_BPS: Bps[] = [25n, 50n, 100n, 200n, 500n].map(\n (n) => Bps(n)\n);\n\n/** Default slippage tolerance (0.5%). */\nexport const DEFAULT_SLIPPAGE_BPS: Bps = Bps(50n);\n\n/**\n * The deployed Superstate swap contract on Ethereum Sepolia, and therefore the\n * ERC-20 spender the user approves there.\n *\n * Exported in its own right for a partner driving `coinlist.superstate.execute`\n * at Level 1, where the contract is an explicit parameter.\n */\nexport const SUPERSTATE_SWAP_CONTRACT_ADDRESS_SEPOLIA: EvmContractAddress =\n EvmContractAddress('0x27E8CE9Ca1A1E98Bc4009F08Dc596bFcf88aCED1');\n\n/**\n * The Superstate swap contract per chain.\n *\n * Mainnet is absent, not zeroed: the production address is not finalized\n * upstream, and a zero address would be approved happily and revert only later.\n * A chain with no entry has no checkout — `useSuperstateSwapCheckoutViewModel`\n * reports it as the flow-level error rather than quoting against nothing.\n */\nconst SUPERSTATE_SWAP_CONTRACT_ADDRESSES: Partial<\n Record<EthereumChain, EvmContractAddress>\n> = {\n ethereum_sepolia: SUPERSTATE_SWAP_CONTRACT_ADDRESS_SEPOLIA,\n};\n\n/**\n * The Superstate swap contract on `chain`, or `null` where none is deployed.\n *\n * TODO(ENG-1734): Ondo now reads its spender off the offer's `swap_contracts`\n * via `swapSpender`, which is why that helper sits in `shared/core/offers/`\n * rather than under a provider. Superstate still resolves from this table, so\n * two mechanisms answer one question. Migrate once frontline serves\n * `swap_contracts` on Superstate offers too.\n */\nexport function superstateSwapContractAddress(\n chain: EthereumChain\n): EvmContractAddress | null {\n return SUPERSTATE_SWAP_CONTRACT_ADDRESSES[chain] ?? null;\n}\n","import { computePrice } from '@/universal/core/checkout/superstate/blockchain/math';\nimport type { SwapQuote } from '@/universal/core/checkout/superstate/blockchain/quote';\nimport {\n formattedUsdPrice,\n NA_AMOUNT_ASSET_UI,\n} from '@/universal/core/shared/blockchain/formatters';\nimport type { FormattedAmountAssetUi } from '@/universal/types/blockchain/ui';\n\n/**\n * Formats a quote's implied price per output token as a USD amount.\n *\n * Lives with the provider rather than in `@/universal/core/shared/blockchain/formatters`\n * because {@link SwapQuote} and {@link computePrice} are Superstate's: the\n * shared formatters stay provider-agnostic.\n */\nexport function formattedPricePerShare(\n quote: SwapQuote | null,\n locale: string\n): FormattedAmountAssetUi {\n if (!quote) return NA_AMOUNT_ASSET_UI;\n return formattedUsdPrice(computePrice(quote), locale);\n}\n","import * as offersApi from '@/universal/api/frontline/offers';\nimport type { UniversalNamespaceContext } from '@/universal/core/shared/namespace-context';\nimport {\n type InternalLogger,\n internalLogger,\n} from '@/universal/core/shared/observability/internal-logger';\nimport type { Offer, OfferId } from '@/universal/types/offer';\nimport type { OfferDetail } from '@/universal/types/offer-detail';\nimport type {\n PaginatedResponse,\n PaginationParams,\n} from '@/universal/types/pagination';\n\n/**\n * Reads over CoinList's offers: the catalogue a user can browse, and the full\n * detail of a single offer.\n *\n * Every method requires an authenticated user and throws\n * {@link NotAuthenticatedError} otherwise. On the server, the same reads are\n * additionally available with an app-level token — see\n * `ServerOffersNamespace`.\n */\nexport interface OffersNamespace {\n /**\n * Fetches every offer, iterating through all pages. Prefer {@link listPage}\n * when you render a paginated list yourself.\n */\n list(): Promise<Offer[]>;\n\n /**\n * Fetches a single page of offers. Pass the previous response's\n * `startingAfter` as `after` to advance.\n */\n listPage(params: PaginationParams): Promise<PaginatedResponse<Offer>>;\n\n /**\n * Fetches the full detail of one offer, including its options. Note this\n * returns {@link OfferDetail} — a richer model than the {@link Offer}\n * summaries {@link list} returns.\n */\n get(id: OfferId): Promise<OfferDetail>;\n}\n\nexport class OffersNamespaceImpl implements OffersNamespace {\n private readonly log: InternalLogger;\n\n constructor(private readonly ctx: UniversalNamespaceContext) {\n this.log = internalLogger(ctx.logger, 'OFFERS');\n }\n\n async list(): Promise<Offer[]> {\n return this.log.wrap('list', undefined, async () => {\n await this.ctx.ensureUserAuthenticated();\n return offersApi.fetchOffers(this.ctx.api, undefined);\n });\n }\n\n async listPage(params: PaginationParams): Promise<PaginatedResponse<Offer>> {\n return this.log.wrap('listPage', params, async () => {\n await this.ctx.ensureUserAuthenticated();\n return offersApi.fetchOffersPage(this.ctx.api, params, undefined);\n });\n }\n\n async get(id: OfferId): Promise<OfferDetail> {\n return this.log.wrap('get', id, async () => {\n await this.ctx.ensureUserAuthenticated();\n return offersApi.fetchOfferDetails(this.ctx.api, id, undefined);\n });\n }\n}\n","import type {\n EthereumChain,\n EvmContractAddress,\n} from '@/universal/types/blockchain/core';\nimport type { OfferSwapContract } from '@/universal/types/offer-detail';\n\n/**\n * The CoinList contract a swap on `chain` settles through, and therefore the\n * ERC-20 spender the user approves - or `null` where this offer cannot be\n * swapped on that chain.\n *\n * Read off the offer rather than held as a constant because the address varies\n * by environment, not only by chain: beta and production both run\n * `ethereum_sepolia` and deploy a different contract on each, and one constant\n * cannot name both. Frontline resolves it per environment and serves the\n * answer on the offer.\n *\n * The array varies per *offer* as well as per chain - frontline drops a chain\n * whose funding and swap tokens do not both resolve for this asset - so an\n * array read for one symbol must not be reused for another. An empty array\n * means \"cannot swap here\", which is an answer rather than an error.\n *\n * A capability rather than a provider slice, and named for the data it reads\n * rather than for its first caller: `swap_contracts` is an offer field, and\n * `universal-core-isolation-and-sharing` would stop Superstate reusing this if\n * it lived under `checkout/ondo/`. Superstate has not migrated yet - it still\n * reads `superstateSwapContractAddress` - so Ondo is the only caller today.\n */\nexport function swapSpender(\n swapContracts: OfferSwapContract[],\n chain: EthereumChain\n): EvmContractAddress | null {\n return (\n swapContracts.find((contract) => contract.chain === chain)?.address ?? null\n );\n}\n","import type {\n Chain,\n EvmContractAddress,\n} from '@/universal/types/blockchain/core';\nimport type { OfferToken } from '@/universal/types/offer';\nimport type { TokenMetadata } from '@/universal/types/token-metadata';\n\n/**\n * One `coinlist.tokens.list()` answer, indexed for the lookups a catalogue\n * makes: by the `(chain, address)` pairs `Offer.tokens` carries.\n *\n * The registry is fetched whole - a few hundred tokens across every chain in\n * one request - and then asked one question per offer. Building the index\n * once is what keeps that a map lookup per card rather than a scan of the\n * snapshot per card. The same rule frontline-web and every other host wrote\n * for themselves, kept here so that none of them has to.\n */\nexport type TokenRegistrySnapshot = {\n /** Every token the snapshot lists, in registry order. */\n readonly tokens: readonly TokenMetadata[];\n /** `chain:address` (address lowercased) to its metadata. */\n readonly byKey: ReadonlyMap<string, TokenMetadata>;\n};\n\n/**\n * A token as a lookup names it. `Chain` rather than `EthereumChain` because\n * `OfferToken.chain` is the wider type; a Solana entry simply never matches,\n * since the snapshot models EVM chains only.\n */\ntype TokenKey = { chain: Chain; address: EvmContractAddress };\n\nexport const TokenRegistrySnapshot = {\n /** Indexes a `tokens.list()` result. `[]` is a valid, empty snapshot. */\n fromTokens: (tokens: readonly TokenMetadata[]): TokenRegistrySnapshot => ({\n tokens,\n byKey: new Map(\n tokens.map((token) => [registryKey(token.identifier), token])\n ),\n }),\n\n /**\n * The registry's entry for one token, or `null` when it lists none. The\n * registry is curated, so `null` is an ordinary answer rather than an\n * error, and callers fall back to their own display defaults.\n */\n get: (\n snapshot: TokenRegistrySnapshot,\n token: TokenKey\n ): TokenMetadata | null => snapshot.byKey.get(registryKey(token)) ?? null,\n\n /**\n * The registry's entry for the token an offer is *about*, or `null` when\n * it lists none of them.\n *\n * Resolution follows {@link offerDisplayTokens}: the token a sale\n * distributes or a swap hands over, never the funding token the user pays\n * with. An offer deployed on several chains carries one such token per\n * chain, and the first the registry knows wins - they are the same asset,\n * so any listed one answers for the offer.\n */\n forOffer: (\n snapshot: TokenRegistrySnapshot,\n offer: { tokens: readonly OfferToken[] }\n ): TokenMetadata | null => {\n for (const token of offerDisplayTokens(offer)) {\n const metadata = TokenRegistrySnapshot.get(snapshot, token);\n if (metadata !== null) {\n return metadata;\n }\n }\n return null;\n },\n};\n\n/**\n * The tokens an offer is *about*, most specific first: every `swap` or\n * `vault` entry, then every `distribution` entry. `funding` entries name what\n * an offer is paid with (USDC, USDT) and never the asset on offer, so they are\n * left out.\n *\n * `swap` and `vault` rank together - no offer type carries both - and outrank\n * `distribution` because a swap or vault product names the token it hands over\n * precisely, while a sale's distribution entry names the same token for a sale\n * product and is accepted as the fallback. The checkout's\n * per-chain lookup and the catalogue's any-chain lookup both read this\n * ordering, which is why it is stated once, here.\n */\nexport function offerDisplayTokens(offer: {\n tokens: readonly OfferToken[];\n}): OfferToken[] {\n const swaps = offer.tokens.filter(\n (token) => token.role === 'swap' || token.role === 'vault'\n );\n const distributions = offer.tokens.filter(\n (token) => token.role === 'distribution'\n );\n return [...swaps, ...distributions];\n}\n\n/**\n * The registry serves EIP-55 checksummed addresses while an offer may carry\n * any casing, so an EVM address compares case-insensitively. Every chain the\n * registry snapshot models is an EVM chain (`TokenMetadata.identifier.chain`\n * is an `EthereumChain`), so lowercasing is safe for every entry it holds.\n */\nfunction registryKey(token: TokenKey): string {\n return `${token.chain}:${token.address.toLowerCase()}`;\n}\n","import type { Hash } from 'viem';\n\n/**\n * A classified wallet or transaction failure. The SDK derives this from\n * whatever the {@link EvmWallet} throws, so hosts get a stable, typed error\n * shape regardless of the wallet library underneath.\n *\n * Lives here rather than beside its classifier because it is the failure half\n * of the {@link EvmWallet} contract, and because both `@/universal` and\n * `@/client` name it: {@link LogCause} carries one, and classifying a thrown\n * error into one needs viem's error classes, which are browser-side.\n */\nexport type WalletError =\n | { type: 'user_rejected' }\n | { type: 'insufficient_funds' }\n | {\n type: 'contract_reverted';\n /**\n * What the contract said, when it said anything: a `require` string, a\n * panic description, a custom error's name, or the four-byte selector of\n * an error the ABI could not decode.\n *\n * `null` when a revert carried no data, since the string the RPC node\n * offers in its place is not the contract's - see `classifyWalletError`.\n * Nullable rather than optional so that every arm has to answer the\n * question: \"it reverted and said nothing\" is a fact about the revert,\n * and a caller should have to read it rather than miss it.\n *\n * Never the custom error's arguments. A name is a compile-time\n * identifier; its operands are runtime values - an address, a balance -\n * and those are `'debug'` material.\n */\n reason: string | null;\n }\n | { type: 'timeout'; hash: Hash }\n | { type: 'unknown'; cause: unknown };\n\n/**\n * A {@link WalletError} as it may be reported above `'debug'`: the\n * classification, never the throw.\n *\n * Only the `unknown` arm differs, and it differs because it is the one arm\n * carrying a value the SDK did not author - the raw error the wallet library\n * threw, whose message and metadata hold the transaction's `from`, `to`,\n * `value` and calldata. The other four carry compile-time constants or a\n * server-minted identifier already, so they pass through as they stand.\n *\n * This exists as a type rather than as a scrub some function remembers to\n * apply: `LogCause` names it, so an arm that grew a raw field would fail the\n * build instead of quietly reaching {@link Logger.error}.\n */\nexport type RedactedWalletError =\n | Exclude<WalletError, { type: 'unknown' }>\n | { type: 'unknown' };\n\nexport const RedactedWalletError = {\n fromWalletError: (error: WalletError): RedactedWalletError =>\n error.type === 'unknown' ? { type: 'unknown' } : error,\n};\n","import type { EvmWalletAddress } from '@/universal/types/blockchain/core';\nimport type {\n EvmWallet,\n ExternalWalletAdapter,\n} from '@/universal/types/blockchain/evm-wallet';\n\n/**\n * The wallets a screen reads for and acts from, supplied by the host.\n *\n * The SDK ships no wallet stack, so wallet discovery and connection are\n * inverted onto the host: it hands over ready {@link EvmWallet}s, and an\n * adapter for connecting an external one when it supports that. Every wallet\n * here is one that can sign for itself, so a screen can broadcast from the\n * wallet that holds a position.\n */\nexport type WalletSelection = {\n /**\n * The user's embedded (custodial) wallets, in display order. Empty when\n * they have none, which is a normal state, not an error.\n */\n embedded: EvmWallet[];\n /** The connected external wallet, or `null` while none is connected. */\n external: EvmWallet | null;\n /**\n * The host's external-wallet connector, or `null` when it offers none - in\n * which case the screen shows no connect affordance at all. A host may\n * still pass a connected `external` without one, to show a wallet the user\n * cannot change from here.\n */\n externalAdapter: ExternalWalletAdapter | null;\n};\n\nexport const WalletSelection = {\n /**\n * Every wallet in display order - embedded first, then the external one -\n * each address once. Addresses compare case-insensitively, so a host that\n * hands the same wallet over as both embedded and external sees it once.\n */\n all: (selection: WalletSelection): EvmWallet[] => {\n const seen = new Set<string>();\n return [\n ...selection.embedded,\n ...(selection.external === null ? [] : [selection.external]),\n ].filter((wallet) => {\n const key = wallet.address.toLowerCase();\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n },\n};\n\n/**\n * Names a wallet for display, e.g. `'CoinList Wallet'` for the user's\n * embedded one. `null` means it has no name, and the screen shows its\n * shortened address instead.\n */\nexport type WalletLabelResolver = (address: EvmWalletAddress) => string | null;\n","import type { UserDto } from '@/universal/types/dto/user';\nimport type { Newtype } from '@/universal/types/newtype';\n\nexport type UserId = Newtype<string, 'UserId'>;\nexport const UserId = (value: string) => value as UserId;\nexport type UserEmail = Newtype<string, 'UserEmail'>;\nexport const UserEmail = (value: string) => value as UserEmail;\n\nexport type User = {\n id: UserId;\n email: UserEmail;\n};\n\nexport const User = {\n fromDto: (dto: UserDto): User => ({\n id: UserId(dto.id),\n email: UserEmail(dto.email),\n }),\n};\n"],"mappings":"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","names":[]}
|