@coinlist-co/react 0.12.2-rc.818bed6 → 0.12.2-rc.8a0d02a

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.
@@ -1,4 +1,4 @@
1
- import { A as AuthorizationCode, C as CodeVerifier, aH as OAuthSession, D as OAuthAccessToken, aI as ClientCredentialsOAuth, aJ as Sender, L as Logger, F as OffersNamespace, a0 as Offer, aK as PaginationParams, aL as PaginatedResponse, i as OfferId, J as OfferDetail, R as RequirementsNamespace, j as OfferOptionId, x as Requirement, y as RequirementsNamespaceImpl, U as UniversalNamespaceContext, t as WalletsNamespace, E as Erc20Namespace, h as CoinListTokenSaleNamespace, S as SuperstateSwapNamespace, m as OndoNamespace, c as CentrifugeNamespace, T as TokensNamespace, z as Config, aM as ClientSecret, Z as Seconds, a9 as PinoLoggerOptions } from '../time-DJU6E8KK.cjs';
1
+ import { A as AuthorizationCode, C as CodeVerifier, aF as OAuthSession, D as OAuthAccessToken, aG as ClientCredentialsOAuth, aH as Sender, L as Logger, F as OffersNamespace, $ as Offer, aI as PaginationParams, aJ as PaginatedResponse, i as OfferId, I as OfferDetail, R as RequirementsNamespace, j as OfferOptionId, x as Requirement, y as RequirementsNamespaceImpl, U as UniversalNamespaceContext, t as WalletsNamespace, E as Erc20Namespace, h as CoinListTokenSaleNamespace, S as SuperstateSwapNamespace, m as OndoNamespace, c as CentrifugeNamespace, T as TokensNamespace, z as Config, aK as ClientSecret, Y as Seconds, a8 as PinoLoggerOptions } from '../time-CVZpAkGY.cjs';
2
2
  import 'viem';
3
3
 
4
4
  interface SessionStore {
@@ -298,11 +298,8 @@ interface CoinListServer {
298
298
  */
299
299
  readonly ondo: OndoNamespace;
300
300
  /**
301
- * Centrifuge vault reads: an indicative quote and the vault-wide ordering
302
- * gate. Both are free to poll.
303
- *
304
- * The quote has no expiry: it is indicative, the fund prices fulfilment
305
- * later, and a countdown would imply a commitment the API does not make.
301
+ * Centrifuge vault reads: the vault-wide ordering gate, and each wallet's
302
+ * position. The status read is free to poll.
306
303
  */
307
304
  readonly centrifuge: CentrifugeNamespace;
308
305
  /**
@@ -1,4 +1,4 @@
1
- import { A as AuthorizationCode, C as CodeVerifier, aH as OAuthSession, D as OAuthAccessToken, aI as ClientCredentialsOAuth, aJ as Sender, L as Logger, F as OffersNamespace, a0 as Offer, aK as PaginationParams, aL as PaginatedResponse, i as OfferId, J as OfferDetail, R as RequirementsNamespace, j as OfferOptionId, x as Requirement, y as RequirementsNamespaceImpl, U as UniversalNamespaceContext, t as WalletsNamespace, E as Erc20Namespace, h as CoinListTokenSaleNamespace, S as SuperstateSwapNamespace, m as OndoNamespace, c as CentrifugeNamespace, T as TokensNamespace, z as Config, aM as ClientSecret, Z as Seconds, a9 as PinoLoggerOptions } from '../time-DJU6E8KK.js';
1
+ import { A as AuthorizationCode, C as CodeVerifier, aF as OAuthSession, D as OAuthAccessToken, aG as ClientCredentialsOAuth, aH as Sender, L as Logger, F as OffersNamespace, $ as Offer, aI as PaginationParams, aJ as PaginatedResponse, i as OfferId, I as OfferDetail, R as RequirementsNamespace, j as OfferOptionId, x as Requirement, y as RequirementsNamespaceImpl, U as UniversalNamespaceContext, t as WalletsNamespace, E as Erc20Namespace, h as CoinListTokenSaleNamespace, S as SuperstateSwapNamespace, m as OndoNamespace, c as CentrifugeNamespace, T as TokensNamespace, z as Config, aK as ClientSecret, Y as Seconds, a8 as PinoLoggerOptions } from '../time-CVZpAkGY.js';
2
2
  import 'viem';
3
3
 
4
4
  interface SessionStore {
@@ -298,11 +298,8 @@ interface CoinListServer {
298
298
  */
299
299
  readonly ondo: OndoNamespace;
300
300
  /**
301
- * Centrifuge vault reads: an indicative quote and the vault-wide ordering
302
- * gate. Both are free to poll.
303
- *
304
- * The quote has no expiry: it is indicative, the fund prices fulfilment
305
- * later, and a countdown would imply a commitment the API does not make.
301
+ * Centrifuge vault reads: the vault-wide ordering gate, and each wallet's
302
+ * position. The status read is free to poll.
306
303
  */
307
304
  readonly centrifuge: CentrifugeNamespace;
308
305
  /**
@@ -7,7 +7,7 @@ import {
7
7
  notifySessionExpired,
8
8
  publicClients,
9
9
  systemTime
10
- } from "../chunk-2HCSM3FB.js";
10
+ } from "../chunk-3WW5FLT7.js";
11
11
  import {
12
12
  API_VERSION,
13
13
  Attributes,
@@ -35,7 +35,7 @@ import {
35
35
  internalLogger,
36
36
  isExpired,
37
37
  isRejectedToken
38
- } from "../chunk-YMDYLYL3.js";
38
+ } from "../chunk-C6GF7MDF.js";
39
39
 
40
40
  // src/server/core/api/api-client.ts
41
41
  var ApiClient = class {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/core/api/api-client.ts","../../src/server/core/session-refresh.ts","../../src/server/shared/errors.ts","../../src/server/core/server-auth-namespace-impl.ts","../../src/server/core/server-offers-namespace.ts","../../src/server/core/server-requirements-namespace.ts","../../src/server/core/coinlist-server.ts","../../src/server/core/observability/pino-server-logger.ts","../../src/server/core/server-auth-namespace.ts"],"sourcesContent":["import { AuthenticatedApiClient } from '@/universal/api/authenticated-api-client';\nimport type { HttpClientConfig, HttpRequest } from '@/universal/api/http';\nimport type { InternalAccessTokenSource } from '@/universal/api/internal-access-token-source';\nimport type { Logger } from '@/universal/types/logger';\n\nexport class ApiClient {\n private readonly client: AuthenticatedApiClient;\n\n constructor(\n config: HttpClientConfig,\n fetchAccessToken: InternalAccessTokenSource,\n logger: Logger | null = null\n ) {\n this.client = new AuthenticatedApiClient(\n config,\n fetchAccessToken,\n [],\n logger\n );\n }\n\n async send<TResponse>(request: HttpRequest): Promise<TResponse> {\n return this.client.send<TResponse>(request);\n }\n}\n","import { systemTime, type TimeEff } from '@/universal/core/shared/effects';\nimport type { Newtype } from '@/universal/types/newtype';\nimport type { ClientId } from '@/universal/types/oauth';\nimport type { OAuthSession } from '@/universal/types/oauth-session';\nimport { EpochMillis, Millis } from '@/universal/types/time';\n\n/**\n * A second rejection of the same refresh token counts as the verdict only this\n * long after the first. A request sent just before the winning refresh's cookie\n * landed can still present the old token a moment later, and a verdict clears\n * the store - a clearing cookie that could overwrite the winner's - so this is\n * deliberately wider than any in-flight request should take.\n */\nexport const VERDICT_MIN_GAP: Millis = Millis.of(5_000);\n\n/**\n * How long a rejection is remembered. A rejection older than this counts as\n * the first again, which can only delay a verdict, never make a false one.\n */\nexport const REJECTION_MEMORY: Millis = Millis.of(60_000);\n\n/**\n * Identifies one session for coordination: the refresh token when it has one,\n * the access token when it does not (there is then nothing to exchange, but\n * the verdict still has to be reached once rather than once per request).\n */\nexport type RefreshKey = Newtype<string, 'RefreshKey'>;\n\nexport const RefreshKey = {\n of: ({\n clientId,\n session,\n }: {\n clientId: ClientId;\n session: OAuthSession;\n }): RefreshKey => {\n const identity =\n session.refreshToken === undefined\n ? ['access', session.accessToken.value]\n : ['refresh', session.refreshToken];\n // JSON quotes and escapes each part, so no client id and token can be\n // split two ways into the same key.\n return JSON.stringify([clientId, ...identity]) as RefreshKey;\n },\n};\n\n/** What one `POST /oauth/token` exchange answered, before it is judged. */\nexport type ExchangeResult =\n | { type: 'renewed'; session: OAuthSession }\n /** `400 invalid_grant`: rotated by a concurrent refresh, expired or revoked. */\n | { type: 'rejected' }\n /** The session carries no refresh token, so there is nothing to exchange. */\n | { type: 'unrenewable' }\n /**\n * 5xx, network failure, `invalid_client`, unparsable body: no answer at all,\n * so whether the session is alive is unknown.\n */\n | { type: 'unknown'; error: unknown };\n\n/**\n * What one exchange came to, judged once for every caller that joined it.\n *\n * - `suspected` - a first rejection, or a repeat inside {@link VERDICT_MIN_GAP}.\n * Write nothing: the rejection may be a race another request won.\n * - `expired` - the verdict. The caller that led the exchange clears its\n * store and notifies the host; the ones that joined only clear.\n * - `already-expired` - the verdict was reached earlier; no exchange was made.\n * Clear, and notify nobody.\n */\nexport type RefreshOutcome =\n | { type: 'renewed'; session: OAuthSession }\n | { type: 'suspected' }\n | { type: 'expired' }\n | { type: 'already-expired' }\n | { type: 'unknown'; error: unknown };\n\n/** What the coordinator remembers about a session it has seen rejected. */\nexport type Rejection =\n | { type: 'suspected'; since: EpochMillis }\n | { type: 'expired'; at: EpochMillis };\n\n/**\n * Deduplicates refresh-token exchanges and remembers rejections across every\n * `CoinListServer` in the process.\n *\n * **Process-wide on purpose.** Hosts build a `CoinListServer` per request,\n * bound to that request's cookies, so a per-instance slot would deduplicate\n * nothing: the herd this exists for is N concurrent requests carrying one\n * refresh token, each with its own server. It is the one sanctioned exception\n * to the SDK's \"no global mutable state\" rule (see ADR-12). An in-flight entry\n * is deleted when it settles. A rejection stops counting after\n * {@link REJECTION_MEMORY} and is swept out at the start of the next\n * refresh of any session, so an idle process keeps at most its last minute of\n * rejections - one small entry per dead refresh token - until traffic resumes.\n *\n * Per JavaScript module instance, not per machine: Next.js middleware and\n * route handlers can each hold their own, and separate processes never share\n * one. Losers across that boundary are what the rejection memory is for.\n *\n * Only the exchange is shared. Each caller still writes the outcome to its own\n * `SessionStore`, because each request has its own cookie jar.\n */\nexport class RefreshCoordinator {\n private readonly inFlight = new Map<RefreshKey, Promise<RefreshOutcome>>();\n private readonly rejections = new Map<RefreshKey, Rejection>();\n\n constructor(private readonly env: TimeEff = systemTime) {}\n\n /**\n * Runs `exchange` for `key` unless one is already in flight, in which case\n * the caller joins it. `joined` tells the caller whether it led: only the\n * leader reports the outcome, so a burst of N produces one verdict, one log\n * line and one `onSessionExpired`.\n */\n refresh(\n key: RefreshKey,\n exchange: () => Promise<ExchangeResult>\n ): { outcome: Promise<RefreshOutcome>; joined: boolean } {\n const existing = this.inFlight.get(key);\n if (existing !== undefined) {\n return { outcome: existing, joined: true };\n }\n const outcome = this.run(key, exchange).finally(() => {\n this.inFlight.delete(key);\n });\n this.inFlight.set(key, outcome);\n return { outcome, joined: false };\n }\n\n private async run(\n key: RefreshKey,\n exchange: () => Promise<ExchangeResult>\n ): Promise<RefreshOutcome> {\n this.forgetStale(this.env.time.now());\n if (this.rejections.get(key)?.type === 'expired') {\n return { type: 'already-expired' };\n }\n\n const result = await exchange();\n switch (result.type) {\n case 'renewed':\n case 'unknown':\n return result;\n case 'rejected':\n case 'unrenewable': {\n const judged = judge({\n result: result.type,\n prior: this.rejections.get(key),\n now: this.env.time.now(),\n });\n this.rejections.set(key, judged.memory);\n return { type: judged.outcome };\n }\n }\n }\n\n private forgetStale(now: EpochMillis): void {\n for (const [key, rejection] of this.rejections) {\n if (EpochMillis.elapsed(rejectedAt(rejection), now) > REJECTION_MEMORY) {\n this.rejections.delete(key);\n }\n }\n }\n}\n\n/**\n * Turns a rejection into a suspicion or a verdict, given what was remembered\n * about the same session. Pure, so the timing rules can be stated as\n * properties over every gap.\n *\n * `unrenewable` is a verdict at once: with no refresh token there is no race\n * to lose. A `rejected` is a verdict only when a remembered suspicion is at\n * least {@link VERDICT_MIN_GAP} old; the caller has already forgotten any\n * older than {@link REJECTION_MEMORY}.\n */\nexport function judge({\n result,\n prior,\n now,\n}: {\n result: 'rejected' | 'unrenewable';\n prior: Rejection | undefined;\n now: EpochMillis;\n}): { outcome: 'suspected' | 'expired'; memory: Rejection } {\n const verdict = {\n outcome: 'expired',\n memory: { type: 'expired', at: now },\n } as const;\n switch (result) {\n case 'unrenewable':\n return verdict;\n case 'rejected':\n switch (prior?.type) {\n case undefined:\n return {\n outcome: 'suspected',\n memory: { type: 'suspected', since: now },\n };\n case 'suspected':\n return EpochMillis.elapsed(prior.since, now) >= VERDICT_MIN_GAP\n ? verdict\n : { outcome: 'suspected', memory: prior };\n case 'expired':\n return { outcome: 'expired', memory: prior };\n }\n }\n}\n\nfunction rejectedAt(rejection: Rejection): EpochMillis {\n switch (rejection.type) {\n case 'suspected':\n return rejection.since;\n case 'expired':\n return rejection.at;\n }\n}\n\n/** The coordinator every `CoinListServer` in this module instance shares. */\nexport const processRefreshCoordinator = new RefreshCoordinator();\n","export class WritableSessionStoreRequiredError extends Error {\n constructor(\n message = 'This operation requires a writable SessionStore. Provide a `setSession` implementation in your SessionStore to persist or clear OAuth sessions.'\n ) {\n super(message);\n this.name = 'WritableSessionStoreRequiredError';\n }\n}\n","import type {\n CompleteOAuthParams,\n ServerAuthNamespace,\n SessionStore,\n} from '@/server/core/server-auth-namespace';\nimport {\n type ExchangeResult,\n type RefreshCoordinator,\n RefreshKey,\n} from '@/server/core/session-refresh';\nimport { WritableSessionStoreRequiredError } from '@/server/shared/errors';\nimport { HttpError } from '@/universal/api/http';\nimport { Attributes } from '@/universal/api/http-attributes';\nimport type { Sender } from '@/universal/api/http-client';\nimport {\n isRejectedToken,\n TokenTrigger,\n} from '@/universal/api/internal-access-token-source';\nimport { notifySessionExpired } from '@/universal/core/auth/session-expired';\nimport type { TimeEff } from '@/universal/core/shared/effects';\nimport {\n type InternalLogger,\n internalLogger,\n} from '@/universal/core/shared/observability/internal-logger';\nimport type { Config } from '@/universal/types/config';\nimport type { OAuthSessionDto } from '@/universal/types/dto/oauth-session';\nimport type { ClientSecret } from '@/universal/types/oauth';\nimport {\n ClientCredentialsOAuth,\n type OAuthAccessToken,\n type OAuthRefreshToken,\n OAuthSession,\n} from '@/universal/types/oauth-session';\nimport {\n EpochMillis,\n isExpired,\n type Millis,\n Seconds,\n} from '@/universal/types/time';\n\ntype SetSession = (session: OAuthSession | null) => Promise<void>;\n\n/** Whether the host's store can persist a renewed session. */\nexport type SessionStoreAccess =\n | { type: 'read-only' }\n | { type: 'writable'; setSession: SetSession };\n\n/**\n * What {@link ServerAuthNamespaceImpl} does with the stored session, decided\n * before any effect runs. See {@link classifyAccessToken}.\n */\nexport type AccessTokenClassification =\n | { type: 'no-session' }\n | { type: 'serve'; token: OAuthAccessToken }\n /** Past its buffer, but a read-only store cannot renew it. */\n | { type: 'serve-stale'; token: OAuthAccessToken }\n /** A read-only store holding the very token the API just refused. */\n | { type: 'refuse' }\n | {\n type: 'refresh';\n session: OAuthSession;\n setSession: SetSession;\n /** See {@link fallbackToken}. */\n fallback: OAuthAccessToken | null;\n };\n\n/**\n * The effects {@link ServerAuthNamespaceImpl} runs on: the clock, the host's\n * session store, and the process-wide refresh coordinator.\n */\nexport type ServerAuthEnv = TimeEff & {\n readonly sessionStore: SessionStore;\n /** Deduplicates exchanges and remembers rejections; see its TSDoc. */\n readonly refreshes: RefreshCoordinator;\n};\n\n/** What {@link ServerAuthNamespaceImpl} needs out of `ServerConfig`. */\nexport type ServerAuthConfig = Config &\n ServerAuthEnv & {\n readonly clientSecret: ClientSecret;\n /** How long before its expiry an access token counts as expired. */\n readonly accessTokenExpiryBufferSeconds: Seconds;\n /** Whether to rethrow errors the SDK could otherwise swallow (e.g. revoke). */\n readonly strict: boolean;\n };\n\n/**\n * The SDK's {@link ServerAuthNamespace}. Internal: not in the `./server`\n * barrel, because {@link tokenFor} and {@link ServerAuthConfig} are the\n * pipeline's seams rather than API a host calls.\n */\nexport class ServerAuthNamespaceImpl implements ServerAuthNamespace {\n private readonly log: InternalLogger;\n\n constructor(\n private readonly api: Sender,\n private readonly config: ServerAuthConfig\n ) {\n this.log = internalLogger(config.logger ?? null, 'AUTH');\n }\n\n async completeOAuth(params: CompleteOAuthParams): Promise<OAuthSession> {\n return this.log.wrap('completeOAuth', undefined, async () => {\n const setSession = this.writableSessionStore();\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: this.config.redirectUri,\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n code_verifier: params.codeVerifier,\n },\n attributes: Attributes.notRetryable(),\n });\n const session = OAuthSession.fromDto(sessionDto);\n await setSession(session);\n return session;\n });\n }\n\n async getAccessToken(): Promise<OAuthAccessToken | null> {\n return this.tokenFor(TokenTrigger.beforeRequest);\n }\n\n /**\n * {@link getAccessToken} with the reason the SDK is asking, which is what\n * the authenticated HTTP pipeline calls: after a 401 the token's own expiry\n * no longer matters, because the API has answered for it.\n */\n async tokenFor(trigger: TokenTrigger): Promise<OAuthAccessToken | null> {\n // Wrapped despite running on every request: `sessionStore.getSession` is\n // host code, and when it throws there is nothing else between the failure\n // and the host's own catch to say which SDK call it came out of.\n return this.log.wrap('getAccessToken', undefined, () =>\n this.readAccessToken(trigger)\n );\n }\n\n private async readAccessToken(\n trigger: TokenTrigger\n ): Promise<OAuthAccessToken | null> {\n const sessionStore = this.config.sessionStore;\n const classification = classifyAccessToken({\n session: await sessionStore.getSession(),\n store: sessionStoreAccess(sessionStore),\n trigger,\n now: this.config.time.now(),\n buffer: Seconds.toMillis(this.config.accessTokenExpiryBufferSeconds),\n });\n switch (classification.type) {\n case 'no-session':\n case 'refuse':\n return null;\n case 'serve':\n return classification.token;\n case 'serve-stale':\n this.log.child({ op: 'getAccessToken' }).warn(() => ({\n msg: 'serving an expired token: the session store is read-only, so it cannot be refreshed',\n }));\n return classification.token;\n case 'refresh':\n return this.refresh(classification);\n }\n }\n\n /**\n * Refreshes through the process-wide coordinator, then acts on the outcome\n * for this caller's own store. The store is written before anything is\n * logged or the host is notified, so `onSessionExpired` always finds it\n * cleared.\n */\n private async refresh({\n session,\n setSession,\n fallback,\n }: Extract<\n AccessTokenClassification,\n { type: 'refresh' }\n >): Promise<OAuthAccessToken | null> {\n const log = this.log.child({ op: 'refresh' });\n const key = RefreshKey.of({ clientId: this.config.clientId, session });\n const { outcome, joined } = this.config.refreshes.refresh(key, () =>\n this.exchange(session.refreshToken)\n );\n if (joined) {\n log.debug(() => ({ msg: 'joined a refresh already in flight' }));\n }\n\n const result = await outcome;\n switch (result.type) {\n case 'renewed':\n await setSession(result.session);\n if (!joined) {\n log.info(() => ({ msg: 'session renewed' }));\n }\n return result.session.accessToken;\n case 'suspected':\n // Writing nothing is the point: a clearing cookie from this response\n // could land after the winner's and sign the user out.\n if (!joined) {\n log.warn(() => ({\n msg: 'refresh rejected; not clearing until it is confirmed',\n }));\n }\n return fallback;\n case 'expired':\n await setSession(null);\n if (!joined) {\n log.info(() => ({\n msg: 'session expired',\n fields: {\n reason:\n session.refreshToken === undefined\n ? 'no-refresh-token'\n : 'refresh-rejected',\n },\n }));\n notifySessionExpired(this.config.onSessionExpired, log);\n }\n return null;\n case 'already-expired':\n await setSession(null);\n return null;\n case 'unknown':\n if (fallback === null) {\n throw result.error;\n }\n if (!joined) {\n log.warning(\n () => ({ msg: 'refresh failed; serving the current token' }),\n result.error\n );\n }\n return fallback;\n }\n }\n\n /**\n * One `POST /oauth/token`, total: every failure comes back as a value, so\n * the coordinator can judge it once for everyone who joined.\n *\n * Never retried. The refresh token is single-use, so a retry of an attempt\n * that reached the server would present a token already rotated away, and\n * its `invalid_grant` would be judged as a rejection of the session.\n */\n private async exchange(\n refreshToken: OAuthRefreshToken | undefined\n ): Promise<ExchangeResult> {\n if (refreshToken === undefined) {\n return { type: 'unrenewable' };\n }\n try {\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n },\n attributes: Attributes.notRetryable(),\n });\n return { type: 'renewed', session: OAuthSession.fromDto(sessionDto) };\n } catch (error) {\n return isInvalidGrant(error)\n ? { type: 'rejected' }\n : { type: 'unknown', error };\n }\n }\n\n async clientCredentials(): Promise<ClientCredentialsOAuth> {\n return this.log.wrap('clientCredentials', undefined, async () => {\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'client_credentials',\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n },\n });\n const session = OAuthSession.fromDto(sessionDto);\n return ClientCredentialsOAuth(session.accessToken);\n });\n }\n\n async logout(): Promise<void> {\n return this.log.wrap('logout', undefined, () => this.revokeAndClear());\n }\n\n private async revokeAndClear(): Promise<void> {\n const setSession = this.writableSessionStore();\n const session = await this.config.sessionStore.getSession();\n if (session == null) return;\n\n try {\n await this.api.send({\n method: 'POST',\n url: `/oauth/revoke`,\n body: {\n token: session.accessToken.value,\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n },\n });\n } catch (err) {\n // Whatever failed - an HTTP error, the network, anything else - the\n // local session is what logs the user out, so only strict callers hear\n // about a revoke that did not reach CoinList.\n if (this.config.strict) {\n throw err;\n }\n this.log\n .child({ op: 'logout' })\n .warning(\n () => ({ msg: 'revoke failed; local session cleared regardless' }),\n err\n );\n }\n await setSession(null);\n }\n\n /**\n * Returns the store's `setSession`, or throws - the guard every\n * session-writing operation shares.\n */\n private writableSessionStore(): SetSession {\n const store = sessionStoreAccess(this.config.sessionStore);\n switch (store.type) {\n case 'writable':\n return store.setSession;\n case 'read-only':\n throw new WritableSessionStoreRequiredError();\n }\n }\n}\n\n/**\n * Decides what to do with the stored session, before any effect runs. Pure,\n * so every combination of store, trigger and clock is a plain table.\n *\n * - A read-only store never refreshes. After a 401 it answers `null` only for\n * the very token that was just refused, which tells the renewal middleware\n * not to replay with it; a store that has moved on since hands its token\n * back.\n * - A writable store refreshes when the token is inside its buffer, or after\n * a 401 for the token it holds. A session with no refresh token whose token\n * still works is served as it is: nothing to renew with, but nothing lost\n * yet either, so the verdict waits for it to actually expire.\n */\nexport function classifyAccessToken({\n session,\n store,\n trigger,\n now,\n buffer,\n}: {\n session: OAuthSession | null;\n store: SessionStoreAccess;\n trigger: TokenTrigger;\n now: EpochMillis;\n buffer: Millis;\n}): AccessTokenClassification {\n if (session === null) {\n return { type: 'no-session' };\n }\n const token = session.accessToken;\n const stale = isExpired({\n expiresAt: EpochMillis.fromDate(token.expiresAt),\n now,\n buffer,\n });\n switch (store.type) {\n case 'read-only':\n return classifyReadOnly(token, trigger, stale);\n case 'writable': {\n if (!needsRefresh(token, trigger, stale)) {\n return { type: 'serve', token };\n }\n const fallback = fallbackToken(token, trigger, now);\n return session.refreshToken === undefined && fallback !== null\n ? { type: 'serve', token: fallback }\n : {\n type: 'refresh',\n session,\n setSession: store.setSession,\n fallback,\n };\n }\n }\n}\n\nfunction classifyReadOnly(\n token: OAuthAccessToken,\n trigger: TokenTrigger,\n stale: boolean\n): AccessTokenClassification {\n switch (trigger.type) {\n case 'after-401':\n return isRejectedToken(token, trigger.rejected)\n ? { type: 'refuse' }\n : { type: 'serve', token };\n case 'before-request':\n return stale ? { type: 'serve-stale', token } : { type: 'serve', token };\n }\n}\n\nfunction sessionStoreAccess(sessionStore: SessionStore): SessionStoreAccess {\n const setSession = sessionStore.setSession?.bind(sessionStore);\n return setSession === undefined\n ? { type: 'read-only' }\n : { type: 'writable', setSession };\n}\n\nfunction needsRefresh(\n token: OAuthAccessToken,\n trigger: TokenTrigger,\n stale: boolean\n): boolean {\n switch (trigger.type) {\n case 'before-request':\n return stale;\n case 'after-401':\n // The store may already hold a newer token than the refused one - a\n // refresh earlier in this same request - and that one is still good.\n return (\n trigger.rejected.type === 'no-user-token' ||\n isRejectedToken(token, trigger.rejected) ||\n stale\n );\n }\n}\n\n/**\n * The token to serve when a refresh cannot produce a new one: the current\n * one, as long as it has not actually expired - it is inside the early-refresh\n * buffer, not past it - and the API has not refused it. The backend keeps a\n * superseded access token valid until its own expiry, so serving it when the\n * refresh fails costs nothing.\n */\nfunction fallbackToken(\n token: OAuthAccessToken,\n trigger: TokenTrigger,\n now: EpochMillis\n): OAuthAccessToken | null {\n const unexpired = !isExpired({\n expiresAt: EpochMillis.fromDate(token.expiresAt),\n now,\n });\n switch (trigger.type) {\n case 'after-401':\n return unexpired && !isRejectedToken(token, trigger.rejected)\n ? token\n : null;\n case 'before-request':\n return unexpired ? token : null;\n }\n}\n\n/**\n * RFC 6749 §5.2's answer for a refresh token that is invalid, expired, revoked\n * or - under rotation - already used. CoinList's server does not say which.\n */\nfunction isInvalidGrant(error: unknown): boolean {\n if (!(error instanceof HttpError) || error.response.status !== 400) {\n return false;\n }\n const body: unknown = error.response.body;\n return (\n typeof body === 'object' &&\n body !== null &&\n 'error' in body &&\n body.error === 'invalid_grant'\n );\n}\n","import type { ServerNamespaceContext } from '@/server/core/server-namespace-context';\nimport * as offersApi from '@/universal/api/frontline/offers';\nimport type { OffersNamespace } from '@/universal/core/offers/offers-namespace';\nimport {\n type InternalLogger,\n internalLogger,\n} from '@/universal/core/shared/observability/internal-logger';\nimport type { ClientCredentialsOAuth } from '@/universal/types/oauth-session';\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 * The server-side offers namespace: the same reads as the shared\n * {@link OffersNamespace}, each additionally callable with an app-level token\n * from `ServerAuthNamespace.clientCredentials` instead of a user session.\n *\n * When a user session exists it takes precedence and `clientCreds` acts as a\n * fallback. Without either, the reads throw {@link NotAuthenticatedError}.\n */\nexport interface ServerOffersNamespace extends OffersNamespace {\n /** @param clientCreds app-level token to read without a user session. */\n list(clientCreds?: ClientCredentialsOAuth): Promise<Offer[]>;\n\n /** @param clientCreds app-level token to read without a user session. */\n listPage(\n params: PaginationParams,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<PaginatedResponse<Offer>>;\n\n /** @param clientCreds app-level token to read without a user session. */\n get(id: OfferId, clientCreds?: ClientCredentialsOAuth): Promise<OfferDetail>;\n}\n\nexport class ServerOffersNamespaceImpl implements ServerOffersNamespace {\n private readonly log: InternalLogger;\n\n constructor(private readonly ctx: ServerNamespaceContext) {\n this.log = internalLogger(ctx.logger, 'OFFERS');\n }\n\n async list(clientCreds?: ClientCredentialsOAuth): Promise<Offer[]> {\n return this.log.wrap('list', clientCreds, async () => {\n await this.ctx.ensureAuthenticated(clientCreds);\n return offersApi.fetchOffers(this.ctx.api, clientCreds);\n });\n }\n\n async listPage(\n params: PaginationParams,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<PaginatedResponse<Offer>> {\n return this.log.wrap('listPage', { params, clientCreds }, async () => {\n await this.ctx.ensureAuthenticated(clientCreds);\n return offersApi.fetchOffersPage(this.ctx.api, params, clientCreds);\n });\n }\n\n async get(\n id: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<OfferDetail> {\n return this.log.wrap('get', { id, clientCreds }, async () => {\n await this.ctx.ensureAuthenticated(clientCreds);\n return offersApi.fetchOfferDetails(this.ctx.api, id, clientCreds);\n });\n }\n}\n","import type { ServerNamespaceContext } from '@/server/core/server-namespace-context';\nimport * as requirementsApi from '@/universal/api/frontline/requirements';\nimport {\n type RequirementsNamespace,\n RequirementsNamespaceImpl,\n} from '@/universal/core/requirements/requirements-namespace';\nimport type { UniversalNamespaceContext } from '@/universal/core/shared/namespace-context';\nimport type { ClientCredentialsOAuth } from '@/universal/types/oauth-session';\nimport type { OfferId } from '@/universal/types/offer';\nimport type { OfferOptionId } from '@/universal/types/offer-detail';\nimport type { Requirement } from '@/universal/types/requirement';\n\n/**\n * The server-side requirements namespace: the same surface as the shared\n * {@link RequirementsNamespace}, except {@link forOffer} — the offer's\n * requirement definitions are app-level data, so it is additionally callable\n * with a token from `ServerAuthNamespace.clientCredentials`.\n *\n * Everything else is per-user data and needs a user session. `handle` is\n * client-only: there is no browser tab to open on a server.\n */\nexport interface ServerRequirementsNamespace extends RequirementsNamespace {\n /** @param clientCreds app-level token to read without a user session. */\n forOffer(\n offerId: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<Record<OfferOptionId, Requirement[]>>;\n}\n\nexport class ServerRequirementsNamespaceImpl\n extends RequirementsNamespaceImpl\n implements ServerRequirementsNamespace\n{\n constructor(\n private readonly serverCtx: UniversalNamespaceContext &\n ServerNamespaceContext\n ) {\n super(serverCtx);\n }\n\n async forOffer(\n offerId: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<Record<OfferOptionId, Requirement[]>> {\n return this.log.wrap('forOffer', { offerId, clientCreds }, async () => {\n await this.serverCtx.ensureAuthenticated(clientCreds);\n return requirementsApi.fetchOfferRequirements(\n this.serverCtx.api,\n offerId,\n clientCreds\n );\n });\n }\n}\n","import { ApiClient } from '@/server/core/api/api-client';\nimport type {\n ServerAuthNamespace,\n SessionStore,\n} from '@/server/core/server-auth-namespace';\nimport { ServerAuthNamespaceImpl } from '@/server/core/server-auth-namespace-impl';\nimport type { ServerNamespaceContext } from '@/server/core/server-namespace-context';\nimport {\n type ServerOffersNamespace,\n ServerOffersNamespaceImpl,\n} from '@/server/core/server-offers-namespace';\nimport {\n type ServerRequirementsNamespace,\n ServerRequirementsNamespaceImpl,\n} from '@/server/core/server-requirements-namespace';\nimport { processRefreshCoordinator } from '@/server/core/session-refresh';\nimport {\n API_VERSION,\n PUBLIC_API_BASE_URL,\n} from '@/universal/api/frontline/config';\nimport { NABU_BASE_URL } from '@/universal/api/nabu/config';\nimport { publicClients } from '@/universal/api/rpc/public-clients';\nimport {\n type CentrifugeNamespace,\n CentrifugeNamespaceImpl,\n} from '@/universal/core/checkout/centrifuge/centrifuge-namespace';\nimport {\n type CoinListTokenSaleNamespace,\n CoinListTokenSaleNamespaceImpl,\n} from '@/universal/core/checkout/coin-list/token-sale-namespace';\nimport {\n type OndoNamespace,\n OndoNamespaceImpl,\n} from '@/universal/core/checkout/ondo/ondo-namespace';\nimport {\n type SuperstateSwapNamespace,\n SuperstateSwapNamespaceImpl,\n} from '@/universal/core/checkout/superstate/swap-namespace';\nimport {\n type Erc20Namespace,\n Erc20NamespaceImpl,\n} from '@/universal/core/shared/blockchain/erc20/erc20-namespace';\nimport { systemTime } from '@/universal/core/shared/effects';\nimport type { UniversalNamespaceContext } from '@/universal/core/shared/namespace-context';\nimport {\n type TokensNamespace,\n TokensNamespaceImpl,\n} from '@/universal/core/tokens/tokens-namespace';\nimport {\n type WalletsNamespace,\n WalletsNamespaceImpl,\n} from '@/universal/core/wallets/wallets-namespace';\nimport type { Config } from '@/universal/types/config';\nimport { NotAuthenticatedError } from '@/universal/types/errors';\nimport type { ClientSecret } from '@/universal/types/oauth';\nimport type { ClientCredentialsOAuth } from '@/universal/types/oauth-session';\nimport { Seconds } from '@/universal/types/time';\n\n/** Buffer before expiry to consider token expired for refresh. */\nconst ACCESS_TOKEN_EXPIRY_BUFFER: Seconds = Seconds.of(60);\n\nexport interface ServerConfig extends Config {\n readonly clientSecret: ClientSecret;\n readonly sessionStore: SessionStore;\n /**\n * How many seconds before its expiry an access token is refreshed. Defaults\n * to 60. Refreshing early moves the work to a single request while the\n * current token still works, and a refresh that fails inside this window\n * keeps serving the current token rather than failing the request.\n *\n * Build it with `Seconds.of(n)`, which throws a `ValidationError` unless\n * `n` is a non-negative integer.\n */\n readonly accessTokenExpiryBufferSeconds?: Seconds;\n /**\n * Whether the SDK throws where it would otherwise recover and log a\n * `'warn'` line. Defaults to `false`.\n *\n * Today it governs one thing: a failed token revoke in `auth.logout()`. By\n * default the session is cleared regardless; with `strict` the failure is\n * rethrown and the session is left untouched.\n */\n readonly strict?: boolean;\n}\n\n/**\n * Server-side CoinList SDK client.\n *\n * Functionality is grouped into namespaces — `coinlist.auth.getAccessToken()`,\n * `coinlist.offers.list()`, `coinlist.requirements.statuses(offerId)`.\n *\n * Operates in one of two modes depending on whether {@link SessionStore}\n * includes a `setSession` implementation:\n *\n * - **Writable store** (`setSession` provided) — full functionality: token\n * refresh, `auth.completeOAuth`, and `auth.logout` all work normally.\n *\n * - **Read-only store** (no `setSession`) — token refresh is skipped entirely,\n * meaning no network call is made and no refresh token is consumed.\n * `auth.completeOAuth` and `auth.logout` throw\n * {@link WritableSessionStoreRequiredError}. `auth.getAccessToken` may\n * return an expired token (see its docs). Use this mode in execution\n * contexts that can read the session but cannot write it back, such as\n * Next.js Server Components.\n *\n * Unless a namespace says otherwise, its methods require a user session and\n * throw {@link NotAuthenticatedError} when there is none. When the SDK could\n * not find out whether there is one - CoinList's token endpoint failing, or\n * the network - they throw that underlying error instead: see\n * {@link ServerAuthNamespace.getAccessToken}.\n */\nexport interface CoinListServer {\n /**\n * OAuth session lifecycle: exchanging an authorization code, serving a valid\n * access token to the browser, minting app-level tokens, and logging out —\n * e.g. `coinlist.auth.getAccessToken()`.\n */\n readonly auth: ServerAuthNamespace;\n\n /**\n * The offer catalogue — e.g. `coinlist.offers.list()`. Every read also\n * accepts an app-level token from {@link ServerAuthNamespace.clientCredentials}\n * so it can run without a user session.\n */\n readonly offers: ServerOffersNamespace;\n\n /**\n * Offer requirements and the operations that satisfy them — e.g.\n * `coinlist.requirements.statuses(offerId)`. `forOffer` also accepts an\n * app-level token; the rest is per-user data and needs a user session.\n */\n readonly requirements: ServerRequirementsNamespace;\n\n /**\n * The user's external wallets: ownership proofs and offer-option bindings —\n * e.g. `coinlist.wallets.list({ offerId, offerOptionId })`.\n */\n readonly wallets: WalletsNamespace;\n\n /**\n * Generic ERC-20 reads (token allowance and balance) — e.g.\n * `coinlist.erc20.getBalance({ ... })`.\n */\n readonly erc20: Erc20Namespace;\n\n /**\n * Token-sale operations: listing, reading, and recording participations —\n * e.g. `coinlist.tokenSale.list()`. The on-chain `execute`\n * flow is client-only and is not exposed here.\n */\n readonly tokenSale: CoinListTokenSaleNamespace;\n\n /**\n * On-chain swap operations: quoting a swap, reading swap-contract state, and\n * allow-listing a proven wallet — e.g.\n * `coinlist.superstate.getOutputToken({ contractAddress, chain })`.\n */\n readonly superstate: SuperstateSwapNamespace;\n\n /**\n * Ondo swap reads: trading status and quoting. Both are free to poll.\n *\n * No CoinList fee is applied to a quote; see {@link OndoNamespace}.\n *\n * This is Ondo's whole surface for now — the wallet-driven half arrives with\n * ENG-1680 and will be client-only, since it needs a wallet to sign with.\n */\n readonly ondo: OndoNamespace;\n\n /**\n * Centrifuge vault reads: an indicative quote and the vault-wide ordering\n * gate. Both are free to poll.\n *\n * The quote has no expiry: it is indicative, the fund prices fulfilment\n * later, and a countdown would imply a commitment the API does not make.\n */\n readonly centrifuge: CentrifugeNamespace;\n\n /**\n * Token display metadata (name, symbol, decimals, logos) from CoinList's\n * public token registry, keyed by chain + contract address — e.g.\n * `coinlist.tokens.get({ chain, address })` for an entry of\n * `Offer.tokens`, or `coinlist.tokens.list()` for the whole\n * catalogue in one request.\n *\n * Public and unauthenticated: unlike the other namespaces, its methods never\n * require a session, so it also works with a read-only store.\n */\n readonly tokens: TokensNamespace;\n}\n\nclass CoinListServerImpl implements CoinListServer {\n private readonly api: ApiClient;\n readonly auth: ServerAuthNamespace;\n readonly offers: ServerOffersNamespace;\n readonly requirements: ServerRequirementsNamespace;\n readonly wallets: WalletsNamespace;\n readonly erc20: Erc20Namespace;\n readonly tokenSale: CoinListTokenSaleNamespace;\n readonly superstate: SuperstateSwapNamespace;\n readonly ondo: OndoNamespace;\n readonly centrifuge: CentrifugeNamespace;\n readonly tokens: TokensNamespace;\n\n constructor(_config: ServerConfig) {\n const logger = _config.logger ?? null;\n this.api = new ApiClient(\n {\n baseUrl: _config.baseUrl ?? PUBLIC_API_BASE_URL,\n xApiVersion: API_VERSION,\n },\n (trigger) => auth.tokenFor(trigger),\n logger\n );\n const auth = new ServerAuthNamespaceImpl(this.api, {\n ..._config,\n accessTokenExpiryBufferSeconds:\n _config.accessTokenExpiryBufferSeconds ?? ACCESS_TOKEN_EXPIRY_BUFFER,\n strict: _config.strict ?? false,\n refreshes: processRefreshCoordinator,\n ...systemTime,\n });\n this.auth = auth;\n\n const ctx: UniversalNamespaceContext & ServerNamespaceContext = {\n api: this.api,\n rpc: publicClients(this.api, _config.rpc, _config.logger ?? null),\n logger,\n ensureUserAuthenticated: () => this.ensureAuthenticated(undefined),\n ensureAuthenticated: (clientCreds) =>\n this.ensureAuthenticated(clientCreds),\n };\n this.offers = new ServerOffersNamespaceImpl(ctx);\n this.requirements = new ServerRequirementsNamespaceImpl(ctx);\n this.wallets = new WalletsNamespaceImpl(ctx);\n this.erc20 = new Erc20NamespaceImpl(ctx);\n this.tokenSale = new CoinListTokenSaleNamespaceImpl(ctx);\n this.superstate = new SuperstateSwapNamespaceImpl(ctx);\n this.ondo = new OndoNamespaceImpl(ctx);\n this.centrifuge = new CentrifugeNamespaceImpl(ctx);\n this.tokens = new TokensNamespaceImpl(\n _config.tokensBaseUrl ?? NABU_BASE_URL,\n logger\n );\n }\n\n /**\n * An app-level token authenticates a request on its own; without one the\n * caller needs a user session.\n */\n private async ensureAuthenticated(\n clientCreds: ClientCredentialsOAuth | undefined\n ): Promise<void> {\n if (clientCreds) return;\n const token = await this.auth.getAccessToken();\n if (token === null) {\n throw new NotAuthenticatedError();\n }\n }\n}\n\nexport function createCoinListServer(config: ServerConfig): CoinListServer {\n return new CoinListServerImpl(config);\n}\n","import { pino } from 'pino';\nimport {\n loggerOverPino,\n PINO_LEVEL,\n SDK_LOGGER_NAME,\n} from '@/universal/core/shared/observability/pino-logger';\nimport type { Logger, PinoLoggerOptions } from '@/universal/types/logger';\n\n/**\n * A {@link Logger} that writes newline-delimited JSON to stdout through pino.\n *\n * ```ts\n * createCoinListServer({\n * ...config,\n * logger: pinoServerLogger({ isDev: false, level: 'info' }),\n * });\n * ```\n *\n * The Node counterpart of {@link pinoClientLogger}, for a route handler, a BFF\n * or SSR. One line per record, which is what every log aggregator ingests\n * without configuration.\n *\n * ## What it deliberately does not do\n *\n * **No transport, and no `pino-pretty`.** A pino transport runs on a worker\n * thread through `thread-stream`, which every bundler has to be told about\n * (`serverExternalPackages` in Next) and which edge runtimes cannot run at\n * all. Pretty-printing is a pipe the host owns rather than a dependency the\n * SDK takes on their behalf:\n *\n * ```sh\n * node server.js | npx pino-pretty\n * ```\n *\n * **No environment-dependent formatting.** `isDev` decides which levels are\n * legal and nothing else, so a development record and a production record are\n * the same shape - what you debug is what you ship.\n *\n * If you already run pino, or want transports, redaction or `destination`,\n * implement {@link Logger} yourself: it is four methods over an event, and\n * `@opentelemetry/instrumentation-pino` patches the pino module rather than an\n * instance, so trace correlation reaches this logger either way.\n *\n * ## Production\n *\n * **Not advised in production**, the same as everywhere else this seam is\n * documented: the SDK's default answer is to leave {@link Config.logger}\n * undefined outside development, staging and incident reproduction. A backend\n * is the least bad place to disregard that - the records go to your process\n * and your sink rather than to a page the end user can read - but it is still\n * a disregard, and the retention and access questions become yours.\n *\n * If you run one, `isDev: false` restricts you to `'error'`, `'warn'` and\n * `'info'` - `{ isDev: false, level: 'debug' }` does not compile, see\n * {@link PinoLoggerOptions}. Those levels are redacted by construction, which\n * is a mechanism the SDK holds itself to; whether that clears your bar is a\n * judgement the SDK is not in a position to make for you.\n *\n * Holds no module-level state: every call builds its own pino instance, and\n * the level is fixed at construction, so two loggers never interfere. Fixed is\n * this implementation's choice, not the seam's - the SDK re-reads\n * {@link Logger.level} before every log call - so if you want a level you can\n * change at runtime, implement {@link Logger} over your own pino instance\n * rather than calling this.\n */\nexport function pinoServerLogger(options: PinoLoggerOptions): Logger {\n return loggerOverPino(\n // `name` as a child binding rather than pino's `name` option: the option\n // is honoured by pino's node build and silently dropped by its browser\n // build, so a binding is the only spelling that identifies the SDK in\n // both environments.\n pino({\n level: PINO_LEVEL[options.level],\n }).child({ name: SDK_LOGGER_NAME }),\n options.level\n );\n}\n","import type { AuthorizationCode, CodeVerifier } from '@/universal/types/oauth';\nimport type {\n ClientCredentialsOAuth,\n OAuthAccessToken,\n OAuthSession,\n} from '@/universal/types/oauth-session';\n\nexport interface SessionStore {\n getSession(): Promise<OAuthSession | null>;\n /**\n * Persists or clears the OAuth session.\n *\n * **Omit this method to create a read-only store.** When absent, the SDK\n * skips token refresh entirely — no network call is made and no refresh\n * token is consumed. This is the correct approach for contexts that can read\n * the session but cannot write it back, such as Next.js Server Components.\n *\n * ⚠️ Do **not** implement this as a no-op (`async () => {}`). If the method\n * is present, the SDK assumes writes succeed: it will fire a token refresh\n * network call, consume the refresh token, then invoke `setSession` — which\n * would silently discard the new session and leave the browser holding an\n * invalidated refresh token. Simply **omit** `setSession` to prevent any\n * refresh from being attempted.\n *\n * {@link ServerAuthNamespace.completeOAuth} and\n * {@link ServerAuthNamespace.logout} always require a writable store and\n * throw {@link WritableSessionStoreRequiredError} if `setSession` is absent.\n */\n setSession?(session: OAuthSession | null): Promise<void>;\n}\n\n/**\n * The empty session store: holds no session, reads nothing, never writes.\n *\n * For server contexts that act without a user — the client-credentials grant\n * (`auth.clientCredentials`) and the public token registry (`tokens`) read\n * nothing from the store, so hosts building those paths need no store of\n * their own.\n *\n * `setSession` is deliberately absent, not a no-op — see\n * {@link SessionStore.setSession} for why a no-op is a bug.\n */\nexport function emptySessionStore(): SessionStore {\n return { getSession: async () => null };\n}\n\n/** The PKCE pair `CoinListClient#auth.completeOAuth` hands to the backend. */\nexport interface CompleteOAuthParams {\n /** The short-lived authorization code parsed out of the redirect URL. */\n code: AuthorizationCode;\n /** The `code_verifier` the browser generated when starting the flow. */\n codeVerifier: CodeVerifier;\n}\n\n/**\n * OAuth session lifecycle on your backend: exchanging authorization codes,\n * keeping the access token fresh, minting app-level tokens, and logging out.\n *\n * This namespace is server-only: every method needs the `clientSecret` and the\n * {@link SessionStore}, neither of which may exist in a browser.\n */\nexport interface ServerAuthNamespace {\n /**\n * Exchanges an authorization code from `CoinListClient#auth.completeOAuth`\n * for an OAuth session and persists it via {@link SessionStore.setSession}.\n *\n * The exchange is never retried automatically: an authorization code is\n * single-use, so a retry of an attempt that reached the server would fail\n * anyway, and could mask why the first one did.\n *\n * @throws {@link WritableSessionStoreRequiredError} if the session store\n * does not provide `setSession`.\n * @throws {@link HttpError} if CoinList refuses the exchange.\n */\n completeOAuth(params: CompleteOAuthParams): Promise<OAuthSession>;\n\n /**\n * Returns a valid access token for the current session, refreshing it if it\n * is expired or near expiry. This is what you serve to\n * {@link ClientConfig.getAccessToken}: answer `null` with \"no session\"\n * (e.g. a 204) and a throw with a failure (e.g. a 503), never the other way\n * round - the browser signs the user out on the first and not the second.\n *\n * **Writable store**: if the token is expired, the SDK exchanges the refresh\n * token for a new session, persists it, and returns the fresh access token.\n * Concurrent calls for the same session in this process share one exchange.\n *\n * A rejected refresh token (`400 invalid_grant`) is only a *suspicion* at\n * first, because the backend answers \"rotated a moment ago by a concurrent\n * request\" and \"expired or revoked\" identically: the SDK writes nothing,\n * so it cannot overwrite the session the winning request just stored, and\n * returns `null` - or the current access token, if that has not actually\n * expired yet. Only a second rejection of the same refresh token, 5 to 60\n * seconds later in this process, is the verdict: the session is cleared via\n * {@link SessionStore.setSession} and {@link Config.onSessionExpired} is\n * called. A browser confirming through your endpoint asks again after one\n * second, so it signs out on its own verdict and not on this one.\n *\n * **Read-only store** (no `setSession`): refresh is skipped entirely. The\n * stored token is returned as-is, even if it is expired — a non-null return\n * value does **not** guarantee the token is accepted by the API. Before\n * making API calls, check `token.expiresAt > new Date()`. Use a writable\n * store (e.g. in a Route Handler) when you need the SDK to renew the session\n * automatically.\n *\n * @returns the access token, or `null` if there is no session, or the\n * session was rejected (suspected or confirmed).\n * @throws the underlying error - an {@link HttpError} for a 5xx or\n * `invalid_client`, the `fetch` error for a network failure - when the SDK\n * could not find out whether a session exists and the current access token\n * has already expired. The session is left untouched. **Never treat this as\n * logged out**: it means \"try again\", and during an outage treating it as\n * logged out would sign out every user at once.\n */\n getAccessToken(): Promise<OAuthAccessToken | null>;\n\n /**\n * Obtains an app-level access token via the OAuth 2.0 `client_credentials`\n * grant (RFC 6749 §4.4). No user is involved: the token authenticates the\n * partner application itself and only grants access to app-level resources\n * such as offers and offer requirements.\n *\n * The token is **not** persisted to the {@link SessionStore} and has no\n * refresh token. It expires at `expiresAt`; once expired, call this method\n * again to obtain a fresh token — the SDK does not renew it automatically.\n *\n * Pass the result to `ServerOffersNamespace` or\n * `ServerRequirementsNamespace.forOffer` to read without a user session.\n */\n clientCredentials(): Promise<ClientCredentialsOAuth>;\n\n /**\n * Revokes the current token via `POST /oauth/revoke` and clears the session.\n * Does nothing when there is no session.\n *\n * The revoke is best-effort: if it fails for any reason - an\n * {@link HttpError}, a network failure, anything else - the failure is\n * logged at `'warn'` and the session is cleared regardless, because the\n * cleared session is what logs the user out. Set `strict` on the server\n * config to rethrow it instead.\n *\n * @throws {@link WritableSessionStoreRequiredError} if the session store does\n * not provide `setSession`.\n * @throws whatever the {@link SessionStore} throws, in either mode. A failed\n * `setSession(null)` means the session was not cleared.\n * @throws the revoke failure, only when `strict` is set. The session is then\n * left untouched, so the call can be retried.\n */\n logout(): Promise<void>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKO,IAAM,YAAN,MAAgB;AAAA,EAGrB,YACE,QACA,kBACA,SAAwB,MACxB;AACA,SAAK,SAAS,IAAI;AAAA,MAChB;AAAA,MACA;AAAA,MACA,CAAC;AAAA,MACD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAgB,SAA0C;AAC9D,WAAO,KAAK,OAAO,KAAgB,OAAO;AAAA,EAC5C;AACF;;;ACXO,IAAM,kBAA0B,OAAO,GAAG,GAAK;AAM/C,IAAM,mBAA2B,OAAO,GAAG,GAAM;AASjD,IAAM,aAAa;AAAA,EACxB,IAAI,CAAC;AAAA,IACH;AAAA,IACA;AAAA,EACF,MAGkB;AAChB,UAAM,WACJ,QAAQ,iBAAiB,SACrB,CAAC,UAAU,QAAQ,YAAY,KAAK,IACpC,CAAC,WAAW,QAAQ,YAAY;AAGtC,WAAO,KAAK,UAAU,CAAC,UAAU,GAAG,QAAQ,CAAC;AAAA,EAC/C;AACF;AA0DO,IAAM,qBAAN,MAAyB;AAAA,EAI9B,YAA6B,MAAe,YAAY;AAA3B;AAH7B,SAAiB,WAAW,oBAAI,IAAyC;AACzE,SAAiB,aAAa,oBAAI,IAA2B;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQzD,QACE,KACA,UACuD;AACvD,UAAM,WAAW,KAAK,SAAS,IAAI,GAAG;AACtC,QAAI,aAAa,QAAW;AAC1B,aAAO,EAAE,SAAS,UAAU,QAAQ,KAAK;AAAA,IAC3C;AACA,UAAM,UAAU,KAAK,IAAI,KAAK,QAAQ,EAAE,QAAQ,MAAM;AACpD,WAAK,SAAS,OAAO,GAAG;AAAA,IAC1B,CAAC;AACD,SAAK,SAAS,IAAI,KAAK,OAAO;AAC9B,WAAO,EAAE,SAAS,QAAQ,MAAM;AAAA,EAClC;AAAA,EAEA,MAAc,IACZ,KACA,UACyB;AACzB,SAAK,YAAY,KAAK,IAAI,KAAK,IAAI,CAAC;AACpC,QAAI,KAAK,WAAW,IAAI,GAAG,GAAG,SAAS,WAAW;AAChD,aAAO,EAAE,MAAM,kBAAkB;AAAA,IACnC;AAEA,UAAM,SAAS,MAAM,SAAS;AAC9B,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK,eAAe;AAClB,cAAM,SAAS,MAAM;AAAA,UACnB,QAAQ,OAAO;AAAA,UACf,OAAO,KAAK,WAAW,IAAI,GAAG;AAAA,UAC9B,KAAK,KAAK,IAAI,KAAK,IAAI;AAAA,QACzB,CAAC;AACD,aAAK,WAAW,IAAI,KAAK,OAAO,MAAM;AACtC,eAAO,EAAE,MAAM,OAAO,QAAQ;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,YAAY,KAAwB;AAC1C,eAAW,CAAC,KAAK,SAAS,KAAK,KAAK,YAAY;AAC9C,UAAI,YAAY,QAAQ,WAAW,SAAS,GAAG,GAAG,IAAI,kBAAkB;AACtE,aAAK,WAAW,OAAO,GAAG;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AACF;AAYO,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAI4D;AAC1D,QAAM,UAAU;AAAA,IACd,SAAS;AAAA,IACT,QAAQ,EAAE,MAAM,WAAW,IAAI,IAAI;AAAA,EACrC;AACA,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,cAAQ,OAAO,MAAM;AAAA,QACnB,KAAK;AACH,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,QAAQ,EAAE,MAAM,aAAa,OAAO,IAAI;AAAA,UAC1C;AAAA,QACF,KAAK;AACH,iBAAO,YAAY,QAAQ,MAAM,OAAO,GAAG,KAAK,kBAC5C,UACA,EAAE,SAAS,aAAa,QAAQ,MAAM;AAAA,QAC5C,KAAK;AACH,iBAAO,EAAE,SAAS,WAAW,QAAQ,MAAM;AAAA,MAC/C;AAAA,EACJ;AACF;AAEA,SAAS,WAAW,WAAmC;AACrD,UAAQ,UAAU,MAAM;AAAA,IACtB,KAAK;AACH,aAAO,UAAU;AAAA,IACnB,KAAK;AACH,aAAO,UAAU;AAAA,EACrB;AACF;AAGO,IAAM,4BAA4B,IAAI,mBAAmB;;;AC1NzD,IAAM,oCAAN,cAAgD,MAAM;AAAA,EAC3D,YACE,UAAU,mJACV;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;;ACoFO,IAAM,0BAAN,MAA6D;AAAA,EAGlE,YACmB,KACA,QACjB;AAFiB;AACA;AAEjB,SAAK,MAAM,eAAe,OAAO,UAAU,MAAM,MAAM;AAAA,EACzD;AAAA,EAEA,MAAM,cAAc,QAAoD;AACtE,WAAO,KAAK,IAAI,KAAK,iBAAiB,QAAW,YAAY;AAC3D,YAAM,aAAa,KAAK,qBAAqB;AAC7C,YAAM,aAAa,MAAM,KAAK,IAAI,KAAsB;AAAA,QACtD,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,MAAM;AAAA,UACJ,YAAY;AAAA,UACZ,MAAM,OAAO;AAAA,UACb,cAAc,KAAK,OAAO;AAAA,UAC1B,WAAW,KAAK,OAAO;AAAA,UACvB,eAAe,KAAK,OAAO;AAAA,UAC3B,eAAe,OAAO;AAAA,QACxB;AAAA,QACA,YAAY,WAAW,aAAa;AAAA,MACtC,CAAC;AACD,YAAM,UAAU,aAAa,QAAQ,UAAU;AAC/C,YAAM,WAAW,OAAO;AACxB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,iBAAmD;AACvD,WAAO,KAAK,SAAS,aAAa,aAAa;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAS,SAAyD;AAItE,WAAO,KAAK,IAAI;AAAA,MAAK;AAAA,MAAkB;AAAA,MAAW,MAChD,KAAK,gBAAgB,OAAO;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,MAAc,gBACZ,SACkC;AAClC,UAAM,eAAe,KAAK,OAAO;AACjC,UAAM,iBAAiB,oBAAoB;AAAA,MACzC,SAAS,MAAM,aAAa,WAAW;AAAA,MACvC,OAAO,mBAAmB,YAAY;AAAA,MACtC;AAAA,MACA,KAAK,KAAK,OAAO,KAAK,IAAI;AAAA,MAC1B,QAAQ,QAAQ,SAAS,KAAK,OAAO,8BAA8B;AAAA,IACrE,CAAC;AACD,YAAQ,eAAe,MAAM;AAAA,MAC3B,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO,eAAe;AAAA,MACxB,KAAK;AACH,aAAK,IAAI,MAAM,EAAE,IAAI,iBAAiB,CAAC,EAAE,KAAK,OAAO;AAAA,UACnD,KAAK;AAAA,QACP,EAAE;AACF,eAAO,eAAe;AAAA,MACxB,KAAK;AACH,eAAO,KAAK,QAAQ,cAAc;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,QAAQ;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAGqC;AACnC,UAAM,MAAM,KAAK,IAAI,MAAM,EAAE,IAAI,UAAU,CAAC;AAC5C,UAAM,MAAM,WAAW,GAAG,EAAE,UAAU,KAAK,OAAO,UAAU,QAAQ,CAAC;AACrE,UAAM,EAAE,SAAS,OAAO,IAAI,KAAK,OAAO,UAAU;AAAA,MAAQ;AAAA,MAAK,MAC7D,KAAK,SAAS,QAAQ,YAAY;AAAA,IACpC;AACA,QAAI,QAAQ;AACV,UAAI,MAAM,OAAO,EAAE,KAAK,qCAAqC,EAAE;AAAA,IACjE;AAEA,UAAM,SAAS,MAAM;AACrB,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK;AACH,cAAM,WAAW,OAAO,OAAO;AAC/B,YAAI,CAAC,QAAQ;AACX,cAAI,KAAK,OAAO,EAAE,KAAK,kBAAkB,EAAE;AAAA,QAC7C;AACA,eAAO,OAAO,QAAQ;AAAA,MACxB,KAAK;AAGH,YAAI,CAAC,QAAQ;AACX,cAAI,KAAK,OAAO;AAAA,YACd,KAAK;AAAA,UACP,EAAE;AAAA,QACJ;AACA,eAAO;AAAA,MACT,KAAK;AACH,cAAM,WAAW,IAAI;AACrB,YAAI,CAAC,QAAQ;AACX,cAAI,KAAK,OAAO;AAAA,YACd,KAAK;AAAA,YACL,QAAQ;AAAA,cACN,QACE,QAAQ,iBAAiB,SACrB,qBACA;AAAA,YACR;AAAA,UACF,EAAE;AACF,+BAAqB,KAAK,OAAO,kBAAkB,GAAG;AAAA,QACxD;AACA,eAAO;AAAA,MACT,KAAK;AACH,cAAM,WAAW,IAAI;AACrB,eAAO;AAAA,MACT,KAAK;AACH,YAAI,aAAa,MAAM;AACrB,gBAAM,OAAO;AAAA,QACf;AACA,YAAI,CAAC,QAAQ;AACX,cAAI;AAAA,YACF,OAAO,EAAE,KAAK,4CAA4C;AAAA,YAC1D,OAAO;AAAA,UACT;AAAA,QACF;AACA,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,SACZ,cACyB;AACzB,QAAI,iBAAiB,QAAW;AAC9B,aAAO,EAAE,MAAM,cAAc;AAAA,IAC/B;AACA,QAAI;AACF,YAAM,aAAa,MAAM,KAAK,IAAI,KAAsB;AAAA,QACtD,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,MAAM;AAAA,UACJ,YAAY;AAAA,UACZ,eAAe;AAAA,UACf,WAAW,KAAK,OAAO;AAAA,UACvB,eAAe,KAAK,OAAO;AAAA,QAC7B;AAAA,QACA,YAAY,WAAW,aAAa;AAAA,MACtC,CAAC;AACD,aAAO,EAAE,MAAM,WAAW,SAAS,aAAa,QAAQ,UAAU,EAAE;AAAA,IACtE,SAAS,OAAO;AACd,aAAO,eAAe,KAAK,IACvB,EAAE,MAAM,WAAW,IACnB,EAAE,MAAM,WAAW,MAAM;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAM,oBAAqD;AACzD,WAAO,KAAK,IAAI,KAAK,qBAAqB,QAAW,YAAY;AAC/D,YAAM,aAAa,MAAM,KAAK,IAAI,KAAsB;AAAA,QACtD,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,MAAM;AAAA,UACJ,YAAY;AAAA,UACZ,WAAW,KAAK,OAAO;AAAA,UACvB,eAAe,KAAK,OAAO;AAAA,QAC7B;AAAA,MACF,CAAC;AACD,YAAM,UAAU,aAAa,QAAQ,UAAU;AAC/C,aAAO,uBAAuB,QAAQ,WAAW;AAAA,IACnD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAwB;AAC5B,WAAO,KAAK,IAAI,KAAK,UAAU,QAAW,MAAM,KAAK,eAAe,CAAC;AAAA,EACvE;AAAA,EAEA,MAAc,iBAAgC;AAC5C,UAAM,aAAa,KAAK,qBAAqB;AAC7C,UAAM,UAAU,MAAM,KAAK,OAAO,aAAa,WAAW;AAC1D,QAAI,WAAW,KAAM;AAErB,QAAI;AACF,YAAM,KAAK,IAAI,KAAK;AAAA,QAClB,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,MAAM;AAAA,UACJ,OAAO,QAAQ,YAAY;AAAA,UAC3B,WAAW,KAAK,OAAO;AAAA,UACvB,eAAe,KAAK,OAAO;AAAA,QAC7B;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AAIZ,UAAI,KAAK,OAAO,QAAQ;AACtB,cAAM;AAAA,MACR;AACA,WAAK,IACF,MAAM,EAAE,IAAI,SAAS,CAAC,EACtB;AAAA,QACC,OAAO,EAAE,KAAK,kDAAkD;AAAA,QAChE;AAAA,MACF;AAAA,IACJ;AACA,UAAM,WAAW,IAAI;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,uBAAmC;AACzC,UAAM,QAAQ,mBAAmB,KAAK,OAAO,YAAY;AACzD,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,eAAO,MAAM;AAAA,MACf,KAAK;AACH,cAAM,IAAI,kCAAkC;AAAA,IAChD;AAAA,EACF;AACF;AAeO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAM8B;AAC5B,MAAI,YAAY,MAAM;AACpB,WAAO,EAAE,MAAM,aAAa;AAAA,EAC9B;AACA,QAAM,QAAQ,QAAQ;AACtB,QAAM,QAAQ,UAAU;AAAA,IACtB,WAAW,YAAY,SAAS,MAAM,SAAS;AAAA,IAC/C;AAAA,IACA;AAAA,EACF,CAAC;AACD,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,iBAAiB,OAAO,SAAS,KAAK;AAAA,IAC/C,KAAK,YAAY;AACf,UAAI,CAAC,aAAa,OAAO,SAAS,KAAK,GAAG;AACxC,eAAO,EAAE,MAAM,SAAS,MAAM;AAAA,MAChC;AACA,YAAM,WAAW,cAAc,OAAO,SAAS,GAAG;AAClD,aAAO,QAAQ,iBAAiB,UAAa,aAAa,OACtD,EAAE,MAAM,SAAS,OAAO,SAAS,IACjC;AAAA,QACE,MAAM;AAAA,QACN;AAAA,QACA,YAAY,MAAM;AAAA,QAClB;AAAA,MACF;AAAA,IACN;AAAA,EACF;AACF;AAEA,SAAS,iBACP,OACA,SACA,OAC2B;AAC3B,UAAQ,QAAQ,MAAM;AAAA,IACpB,KAAK;AACH,aAAO,gBAAgB,OAAO,QAAQ,QAAQ,IAC1C,EAAE,MAAM,SAAS,IACjB,EAAE,MAAM,SAAS,MAAM;AAAA,IAC7B,KAAK;AACH,aAAO,QAAQ,EAAE,MAAM,eAAe,MAAM,IAAI,EAAE,MAAM,SAAS,MAAM;AAAA,EAC3E;AACF;AAEA,SAAS,mBAAmB,cAAgD;AAC1E,QAAM,aAAa,aAAa,YAAY,KAAK,YAAY;AAC7D,SAAO,eAAe,SAClB,EAAE,MAAM,YAAY,IACpB,EAAE,MAAM,YAAY,WAAW;AACrC;AAEA,SAAS,aACP,OACA,SACA,OACS;AACT,UAAQ,QAAQ,MAAM;AAAA,IACpB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAGH,aACE,QAAQ,SAAS,SAAS,mBAC1B,gBAAgB,OAAO,QAAQ,QAAQ,KACvC;AAAA,EAEN;AACF;AASA,SAAS,cACP,OACA,SACA,KACyB;AACzB,QAAM,YAAY,CAAC,UAAU;AAAA,IAC3B,WAAW,YAAY,SAAS,MAAM,SAAS;AAAA,IAC/C;AAAA,EACF,CAAC;AACD,UAAQ,QAAQ,MAAM;AAAA,IACpB,KAAK;AACH,aAAO,aAAa,CAAC,gBAAgB,OAAO,QAAQ,QAAQ,IACxD,QACA;AAAA,IACN,KAAK;AACH,aAAO,YAAY,QAAQ;AAAA,EAC/B;AACF;AAMA,SAAS,eAAe,OAAyB;AAC/C,MAAI,EAAE,iBAAiB,cAAc,MAAM,SAAS,WAAW,KAAK;AAClE,WAAO;AAAA,EACT;AACA,QAAM,OAAgB,MAAM,SAAS;AACrC,SACE,OAAO,SAAS,YAChB,SAAS,QACT,WAAW,QACX,KAAK,UAAU;AAEnB;;;ACzbO,IAAM,4BAAN,MAAiE;AAAA,EAGtE,YAA6B,KAA6B;AAA7B;AAC3B,SAAK,MAAM,eAAe,IAAI,QAAQ,QAAQ;AAAA,EAChD;AAAA,EAEA,MAAM,KAAK,aAAwD;AACjE,WAAO,KAAK,IAAI,KAAK,QAAQ,aAAa,YAAY;AACpD,YAAM,KAAK,IAAI,oBAAoB,WAAW;AAC9C,aAAiB,YAAY,KAAK,IAAI,KAAK,WAAW;AAAA,IACxD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SACJ,QACA,aACmC;AACnC,WAAO,KAAK,IAAI,KAAK,YAAY,EAAE,QAAQ,YAAY,GAAG,YAAY;AACpE,YAAM,KAAK,IAAI,oBAAoB,WAAW;AAC9C,aAAiB,gBAAgB,KAAK,IAAI,KAAK,QAAQ,WAAW;AAAA,IACpE,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IACJ,IACA,aACsB;AACtB,WAAO,KAAK,IAAI,KAAK,OAAO,EAAE,IAAI,YAAY,GAAG,YAAY;AAC3D,YAAM,KAAK,IAAI,oBAAoB,WAAW;AAC9C,aAAiB,kBAAkB,KAAK,IAAI,KAAK,IAAI,WAAW;AAAA,IAClE,CAAC;AAAA,EACH;AACF;;;ACzCO,IAAM,kCAAN,cACG,0BAEV;AAAA,EACE,YACmB,WAEjB;AACA,UAAM,SAAS;AAHE;AAAA,EAInB;AAAA,EAEA,MAAM,SACJ,SACA,aAC+C;AAC/C,WAAO,KAAK,IAAI,KAAK,YAAY,EAAE,SAAS,YAAY,GAAG,YAAY;AACrE,YAAM,KAAK,UAAU,oBAAoB,WAAW;AACpD,aAAuB;AAAA,QACrB,KAAK,UAAU;AAAA,QACf;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACMA,IAAM,6BAAsC,QAAQ,GAAG,EAAE;AAoIzD,IAAM,qBAAN,MAAmD;AAAA,EAajD,YAAY,SAAuB;AACjC,UAAM,SAAS,QAAQ,UAAU;AACjC,SAAK,MAAM,IAAI;AAAA,MACb;AAAA,QACE,SAAS,QAAQ,WAAW;AAAA,QAC5B,aAAa;AAAA,MACf;AAAA,MACA,CAAC,YAAY,KAAK,SAAS,OAAO;AAAA,MAClC;AAAA,IACF;AACA,UAAM,OAAO,IAAI,wBAAwB,KAAK,KAAK;AAAA,MACjD,GAAG;AAAA,MACH,gCACE,QAAQ,kCAAkC;AAAA,MAC5C,QAAQ,QAAQ,UAAU;AAAA,MAC1B,WAAW;AAAA,MACX,GAAG;AAAA,IACL,CAAC;AACD,SAAK,OAAO;AAEZ,UAAM,MAA0D;AAAA,MAC9D,KAAK,KAAK;AAAA,MACV,KAAK,cAAc,KAAK,KAAK,QAAQ,KAAK,QAAQ,UAAU,IAAI;AAAA,MAChE;AAAA,MACA,yBAAyB,MAAM,KAAK,oBAAoB,MAAS;AAAA,MACjE,qBAAqB,CAAC,gBACpB,KAAK,oBAAoB,WAAW;AAAA,IACxC;AACA,SAAK,SAAS,IAAI,0BAA0B,GAAG;AAC/C,SAAK,eAAe,IAAI,gCAAgC,GAAG;AAC3D,SAAK,UAAU,IAAI,qBAAqB,GAAG;AAC3C,SAAK,QAAQ,IAAI,mBAAmB,GAAG;AACvC,SAAK,YAAY,IAAI,+BAA+B,GAAG;AACvD,SAAK,aAAa,IAAI,4BAA4B,GAAG;AACrD,SAAK,OAAO,IAAI,kBAAkB,GAAG;AACrC,SAAK,aAAa,IAAI,wBAAwB,GAAG;AACjD,SAAK,SAAS,IAAI;AAAA,MAChB,QAAQ,iBAAiB;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,oBACZ,aACe;AACf,QAAI,YAAa;AACjB,UAAM,QAAQ,MAAM,KAAK,KAAK,eAAe;AAC7C,QAAI,UAAU,MAAM;AAClB,YAAM,IAAI,sBAAsB;AAAA,IAClC;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB,QAAsC;AACzE,SAAO,IAAI,mBAAmB,MAAM;AACtC;;;ACvQA,SAAS,YAAY;AAiEd,SAAS,iBAAiB,SAAoC;AACnE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,KAAK;AAAA,MACH,OAAO,WAAW,QAAQ,KAAK;AAAA,IACjC,CAAC,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAAA,IAClC,QAAQ;AAAA,EACV;AACF;;;AClCO,SAAS,oBAAkC;AAChD,SAAO,EAAE,YAAY,YAAY,KAAK;AACxC;","names":[]}
1
+ {"version":3,"sources":["../../src/server/core/api/api-client.ts","../../src/server/core/session-refresh.ts","../../src/server/shared/errors.ts","../../src/server/core/server-auth-namespace-impl.ts","../../src/server/core/server-offers-namespace.ts","../../src/server/core/server-requirements-namespace.ts","../../src/server/core/coinlist-server.ts","../../src/server/core/observability/pino-server-logger.ts","../../src/server/core/server-auth-namespace.ts"],"sourcesContent":["import { AuthenticatedApiClient } from '@/universal/api/authenticated-api-client';\nimport type { HttpClientConfig, HttpRequest } from '@/universal/api/http';\nimport type { InternalAccessTokenSource } from '@/universal/api/internal-access-token-source';\nimport type { Logger } from '@/universal/types/logger';\n\nexport class ApiClient {\n private readonly client: AuthenticatedApiClient;\n\n constructor(\n config: HttpClientConfig,\n fetchAccessToken: InternalAccessTokenSource,\n logger: Logger | null = null\n ) {\n this.client = new AuthenticatedApiClient(\n config,\n fetchAccessToken,\n [],\n logger\n );\n }\n\n async send<TResponse>(request: HttpRequest): Promise<TResponse> {\n return this.client.send<TResponse>(request);\n }\n}\n","import { systemTime, type TimeEff } from '@/universal/core/shared/effects';\nimport type { Newtype } from '@/universal/types/newtype';\nimport type { ClientId } from '@/universal/types/oauth';\nimport type { OAuthSession } from '@/universal/types/oauth-session';\nimport { EpochMillis, Millis } from '@/universal/types/time';\n\n/**\n * A second rejection of the same refresh token counts as the verdict only this\n * long after the first. A request sent just before the winning refresh's cookie\n * landed can still present the old token a moment later, and a verdict clears\n * the store - a clearing cookie that could overwrite the winner's - so this is\n * deliberately wider than any in-flight request should take.\n */\nexport const VERDICT_MIN_GAP: Millis = Millis.of(5_000);\n\n/**\n * How long a rejection is remembered. A rejection older than this counts as\n * the first again, which can only delay a verdict, never make a false one.\n */\nexport const REJECTION_MEMORY: Millis = Millis.of(60_000);\n\n/**\n * Identifies one session for coordination: the refresh token when it has one,\n * the access token when it does not (there is then nothing to exchange, but\n * the verdict still has to be reached once rather than once per request).\n */\nexport type RefreshKey = Newtype<string, 'RefreshKey'>;\n\nexport const RefreshKey = {\n of: ({\n clientId,\n session,\n }: {\n clientId: ClientId;\n session: OAuthSession;\n }): RefreshKey => {\n const identity =\n session.refreshToken === undefined\n ? ['access', session.accessToken.value]\n : ['refresh', session.refreshToken];\n // JSON quotes and escapes each part, so no client id and token can be\n // split two ways into the same key.\n return JSON.stringify([clientId, ...identity]) as RefreshKey;\n },\n};\n\n/** What one `POST /oauth/token` exchange answered, before it is judged. */\nexport type ExchangeResult =\n | { type: 'renewed'; session: OAuthSession }\n /** `400 invalid_grant`: rotated by a concurrent refresh, expired or revoked. */\n | { type: 'rejected' }\n /** The session carries no refresh token, so there is nothing to exchange. */\n | { type: 'unrenewable' }\n /**\n * 5xx, network failure, `invalid_client`, unparsable body: no answer at all,\n * so whether the session is alive is unknown.\n */\n | { type: 'unknown'; error: unknown };\n\n/**\n * What one exchange came to, judged once for every caller that joined it.\n *\n * - `suspected` - a first rejection, or a repeat inside {@link VERDICT_MIN_GAP}.\n * Write nothing: the rejection may be a race another request won.\n * - `expired` - the verdict. The caller that led the exchange clears its\n * store and notifies the host; the ones that joined only clear.\n * - `already-expired` - the verdict was reached earlier; no exchange was made.\n * Clear, and notify nobody.\n */\nexport type RefreshOutcome =\n | { type: 'renewed'; session: OAuthSession }\n | { type: 'suspected' }\n | { type: 'expired' }\n | { type: 'already-expired' }\n | { type: 'unknown'; error: unknown };\n\n/** What the coordinator remembers about a session it has seen rejected. */\nexport type Rejection =\n | { type: 'suspected'; since: EpochMillis }\n | { type: 'expired'; at: EpochMillis };\n\n/**\n * Deduplicates refresh-token exchanges and remembers rejections across every\n * `CoinListServer` in the process.\n *\n * **Process-wide on purpose.** Hosts build a `CoinListServer` per request,\n * bound to that request's cookies, so a per-instance slot would deduplicate\n * nothing: the herd this exists for is N concurrent requests carrying one\n * refresh token, each with its own server. It is the one sanctioned exception\n * to the SDK's \"no global mutable state\" rule (see ADR-12). An in-flight entry\n * is deleted when it settles. A rejection stops counting after\n * {@link REJECTION_MEMORY} and is swept out at the start of the next\n * refresh of any session, so an idle process keeps at most its last minute of\n * rejections - one small entry per dead refresh token - until traffic resumes.\n *\n * Per JavaScript module instance, not per machine: Next.js middleware and\n * route handlers can each hold their own, and separate processes never share\n * one. Losers across that boundary are what the rejection memory is for.\n *\n * Only the exchange is shared. Each caller still writes the outcome to its own\n * `SessionStore`, because each request has its own cookie jar.\n */\nexport class RefreshCoordinator {\n private readonly inFlight = new Map<RefreshKey, Promise<RefreshOutcome>>();\n private readonly rejections = new Map<RefreshKey, Rejection>();\n\n constructor(private readonly env: TimeEff = systemTime) {}\n\n /**\n * Runs `exchange` for `key` unless one is already in flight, in which case\n * the caller joins it. `joined` tells the caller whether it led: only the\n * leader reports the outcome, so a burst of N produces one verdict, one log\n * line and one `onSessionExpired`.\n */\n refresh(\n key: RefreshKey,\n exchange: () => Promise<ExchangeResult>\n ): { outcome: Promise<RefreshOutcome>; joined: boolean } {\n const existing = this.inFlight.get(key);\n if (existing !== undefined) {\n return { outcome: existing, joined: true };\n }\n const outcome = this.run(key, exchange).finally(() => {\n this.inFlight.delete(key);\n });\n this.inFlight.set(key, outcome);\n return { outcome, joined: false };\n }\n\n private async run(\n key: RefreshKey,\n exchange: () => Promise<ExchangeResult>\n ): Promise<RefreshOutcome> {\n this.forgetStale(this.env.time.now());\n if (this.rejections.get(key)?.type === 'expired') {\n return { type: 'already-expired' };\n }\n\n const result = await exchange();\n switch (result.type) {\n case 'renewed':\n case 'unknown':\n return result;\n case 'rejected':\n case 'unrenewable': {\n const judged = judge({\n result: result.type,\n prior: this.rejections.get(key),\n now: this.env.time.now(),\n });\n this.rejections.set(key, judged.memory);\n return { type: judged.outcome };\n }\n }\n }\n\n private forgetStale(now: EpochMillis): void {\n for (const [key, rejection] of this.rejections) {\n if (EpochMillis.elapsed(rejectedAt(rejection), now) > REJECTION_MEMORY) {\n this.rejections.delete(key);\n }\n }\n }\n}\n\n/**\n * Turns a rejection into a suspicion or a verdict, given what was remembered\n * about the same session. Pure, so the timing rules can be stated as\n * properties over every gap.\n *\n * `unrenewable` is a verdict at once: with no refresh token there is no race\n * to lose. A `rejected` is a verdict only when a remembered suspicion is at\n * least {@link VERDICT_MIN_GAP} old; the caller has already forgotten any\n * older than {@link REJECTION_MEMORY}.\n */\nexport function judge({\n result,\n prior,\n now,\n}: {\n result: 'rejected' | 'unrenewable';\n prior: Rejection | undefined;\n now: EpochMillis;\n}): { outcome: 'suspected' | 'expired'; memory: Rejection } {\n const verdict = {\n outcome: 'expired',\n memory: { type: 'expired', at: now },\n } as const;\n switch (result) {\n case 'unrenewable':\n return verdict;\n case 'rejected':\n switch (prior?.type) {\n case undefined:\n return {\n outcome: 'suspected',\n memory: { type: 'suspected', since: now },\n };\n case 'suspected':\n return EpochMillis.elapsed(prior.since, now) >= VERDICT_MIN_GAP\n ? verdict\n : { outcome: 'suspected', memory: prior };\n case 'expired':\n return { outcome: 'expired', memory: prior };\n }\n }\n}\n\nfunction rejectedAt(rejection: Rejection): EpochMillis {\n switch (rejection.type) {\n case 'suspected':\n return rejection.since;\n case 'expired':\n return rejection.at;\n }\n}\n\n/** The coordinator every `CoinListServer` in this module instance shares. */\nexport const processRefreshCoordinator = new RefreshCoordinator();\n","export class WritableSessionStoreRequiredError extends Error {\n constructor(\n message = 'This operation requires a writable SessionStore. Provide a `setSession` implementation in your SessionStore to persist or clear OAuth sessions.'\n ) {\n super(message);\n this.name = 'WritableSessionStoreRequiredError';\n }\n}\n","import type {\n CompleteOAuthParams,\n ServerAuthNamespace,\n SessionStore,\n} from '@/server/core/server-auth-namespace';\nimport {\n type ExchangeResult,\n type RefreshCoordinator,\n RefreshKey,\n} from '@/server/core/session-refresh';\nimport { WritableSessionStoreRequiredError } from '@/server/shared/errors';\nimport { HttpError } from '@/universal/api/http';\nimport { Attributes } from '@/universal/api/http-attributes';\nimport type { Sender } from '@/universal/api/http-client';\nimport {\n isRejectedToken,\n TokenTrigger,\n} from '@/universal/api/internal-access-token-source';\nimport { notifySessionExpired } from '@/universal/core/auth/session-expired';\nimport type { TimeEff } from '@/universal/core/shared/effects';\nimport {\n type InternalLogger,\n internalLogger,\n} from '@/universal/core/shared/observability/internal-logger';\nimport type { Config } from '@/universal/types/config';\nimport type { OAuthSessionDto } from '@/universal/types/dto/oauth-session';\nimport type { ClientSecret } from '@/universal/types/oauth';\nimport {\n ClientCredentialsOAuth,\n type OAuthAccessToken,\n type OAuthRefreshToken,\n OAuthSession,\n} from '@/universal/types/oauth-session';\nimport {\n EpochMillis,\n isExpired,\n type Millis,\n Seconds,\n} from '@/universal/types/time';\n\ntype SetSession = (session: OAuthSession | null) => Promise<void>;\n\n/** Whether the host's store can persist a renewed session. */\nexport type SessionStoreAccess =\n | { type: 'read-only' }\n | { type: 'writable'; setSession: SetSession };\n\n/**\n * What {@link ServerAuthNamespaceImpl} does with the stored session, decided\n * before any effect runs. See {@link classifyAccessToken}.\n */\nexport type AccessTokenClassification =\n | { type: 'no-session' }\n | { type: 'serve'; token: OAuthAccessToken }\n /** Past its buffer, but a read-only store cannot renew it. */\n | { type: 'serve-stale'; token: OAuthAccessToken }\n /** A read-only store holding the very token the API just refused. */\n | { type: 'refuse' }\n | {\n type: 'refresh';\n session: OAuthSession;\n setSession: SetSession;\n /** See {@link fallbackToken}. */\n fallback: OAuthAccessToken | null;\n };\n\n/**\n * The effects {@link ServerAuthNamespaceImpl} runs on: the clock, the host's\n * session store, and the process-wide refresh coordinator.\n */\nexport type ServerAuthEnv = TimeEff & {\n readonly sessionStore: SessionStore;\n /** Deduplicates exchanges and remembers rejections; see its TSDoc. */\n readonly refreshes: RefreshCoordinator;\n};\n\n/** What {@link ServerAuthNamespaceImpl} needs out of `ServerConfig`. */\nexport type ServerAuthConfig = Config &\n ServerAuthEnv & {\n readonly clientSecret: ClientSecret;\n /** How long before its expiry an access token counts as expired. */\n readonly accessTokenExpiryBufferSeconds: Seconds;\n /** Whether to rethrow errors the SDK could otherwise swallow (e.g. revoke). */\n readonly strict: boolean;\n };\n\n/**\n * The SDK's {@link ServerAuthNamespace}. Internal: not in the `./server`\n * barrel, because {@link tokenFor} and {@link ServerAuthConfig} are the\n * pipeline's seams rather than API a host calls.\n */\nexport class ServerAuthNamespaceImpl implements ServerAuthNamespace {\n private readonly log: InternalLogger;\n\n constructor(\n private readonly api: Sender,\n private readonly config: ServerAuthConfig\n ) {\n this.log = internalLogger(config.logger ?? null, 'AUTH');\n }\n\n async completeOAuth(params: CompleteOAuthParams): Promise<OAuthSession> {\n return this.log.wrap('completeOAuth', undefined, async () => {\n const setSession = this.writableSessionStore();\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: this.config.redirectUri,\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n code_verifier: params.codeVerifier,\n },\n attributes: Attributes.notRetryable(),\n });\n const session = OAuthSession.fromDto(sessionDto);\n await setSession(session);\n return session;\n });\n }\n\n async getAccessToken(): Promise<OAuthAccessToken | null> {\n return this.tokenFor(TokenTrigger.beforeRequest);\n }\n\n /**\n * {@link getAccessToken} with the reason the SDK is asking, which is what\n * the authenticated HTTP pipeline calls: after a 401 the token's own expiry\n * no longer matters, because the API has answered for it.\n */\n async tokenFor(trigger: TokenTrigger): Promise<OAuthAccessToken | null> {\n // Wrapped despite running on every request: `sessionStore.getSession` is\n // host code, and when it throws there is nothing else between the failure\n // and the host's own catch to say which SDK call it came out of.\n return this.log.wrap('getAccessToken', undefined, () =>\n this.readAccessToken(trigger)\n );\n }\n\n private async readAccessToken(\n trigger: TokenTrigger\n ): Promise<OAuthAccessToken | null> {\n const sessionStore = this.config.sessionStore;\n const classification = classifyAccessToken({\n session: await sessionStore.getSession(),\n store: sessionStoreAccess(sessionStore),\n trigger,\n now: this.config.time.now(),\n buffer: Seconds.toMillis(this.config.accessTokenExpiryBufferSeconds),\n });\n switch (classification.type) {\n case 'no-session':\n case 'refuse':\n return null;\n case 'serve':\n return classification.token;\n case 'serve-stale':\n this.log.child({ op: 'getAccessToken' }).warn(() => ({\n msg: 'serving an expired token: the session store is read-only, so it cannot be refreshed',\n }));\n return classification.token;\n case 'refresh':\n return this.refresh(classification);\n }\n }\n\n /**\n * Refreshes through the process-wide coordinator, then acts on the outcome\n * for this caller's own store. The store is written before anything is\n * logged or the host is notified, so `onSessionExpired` always finds it\n * cleared.\n */\n private async refresh({\n session,\n setSession,\n fallback,\n }: Extract<\n AccessTokenClassification,\n { type: 'refresh' }\n >): Promise<OAuthAccessToken | null> {\n const log = this.log.child({ op: 'refresh' });\n const key = RefreshKey.of({ clientId: this.config.clientId, session });\n const { outcome, joined } = this.config.refreshes.refresh(key, () =>\n this.exchange(session.refreshToken)\n );\n if (joined) {\n log.debug(() => ({ msg: 'joined a refresh already in flight' }));\n }\n\n const result = await outcome;\n switch (result.type) {\n case 'renewed':\n await setSession(result.session);\n if (!joined) {\n log.info(() => ({ msg: 'session renewed' }));\n }\n return result.session.accessToken;\n case 'suspected':\n // Writing nothing is the point: a clearing cookie from this response\n // could land after the winner's and sign the user out.\n if (!joined) {\n log.warn(() => ({\n msg: 'refresh rejected; not clearing until it is confirmed',\n }));\n }\n return fallback;\n case 'expired':\n await setSession(null);\n if (!joined) {\n log.info(() => ({\n msg: 'session expired',\n fields: {\n reason:\n session.refreshToken === undefined\n ? 'no-refresh-token'\n : 'refresh-rejected',\n },\n }));\n notifySessionExpired(this.config.onSessionExpired, log);\n }\n return null;\n case 'already-expired':\n await setSession(null);\n return null;\n case 'unknown':\n if (fallback === null) {\n throw result.error;\n }\n if (!joined) {\n log.warning(\n () => ({ msg: 'refresh failed; serving the current token' }),\n result.error\n );\n }\n return fallback;\n }\n }\n\n /**\n * One `POST /oauth/token`, total: every failure comes back as a value, so\n * the coordinator can judge it once for everyone who joined.\n *\n * Never retried. The refresh token is single-use, so a retry of an attempt\n * that reached the server would present a token already rotated away, and\n * its `invalid_grant` would be judged as a rejection of the session.\n */\n private async exchange(\n refreshToken: OAuthRefreshToken | undefined\n ): Promise<ExchangeResult> {\n if (refreshToken === undefined) {\n return { type: 'unrenewable' };\n }\n try {\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n },\n attributes: Attributes.notRetryable(),\n });\n return { type: 'renewed', session: OAuthSession.fromDto(sessionDto) };\n } catch (error) {\n return isInvalidGrant(error)\n ? { type: 'rejected' }\n : { type: 'unknown', error };\n }\n }\n\n async clientCredentials(): Promise<ClientCredentialsOAuth> {\n return this.log.wrap('clientCredentials', undefined, async () => {\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'client_credentials',\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n },\n });\n const session = OAuthSession.fromDto(sessionDto);\n return ClientCredentialsOAuth(session.accessToken);\n });\n }\n\n async logout(): Promise<void> {\n return this.log.wrap('logout', undefined, () => this.revokeAndClear());\n }\n\n private async revokeAndClear(): Promise<void> {\n const setSession = this.writableSessionStore();\n const session = await this.config.sessionStore.getSession();\n if (session == null) return;\n\n try {\n await this.api.send({\n method: 'POST',\n url: `/oauth/revoke`,\n body: {\n token: session.accessToken.value,\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n },\n });\n } catch (err) {\n // Whatever failed - an HTTP error, the network, anything else - the\n // local session is what logs the user out, so only strict callers hear\n // about a revoke that did not reach CoinList.\n if (this.config.strict) {\n throw err;\n }\n this.log\n .child({ op: 'logout' })\n .warning(\n () => ({ msg: 'revoke failed; local session cleared regardless' }),\n err\n );\n }\n await setSession(null);\n }\n\n /**\n * Returns the store's `setSession`, or throws - the guard every\n * session-writing operation shares.\n */\n private writableSessionStore(): SetSession {\n const store = sessionStoreAccess(this.config.sessionStore);\n switch (store.type) {\n case 'writable':\n return store.setSession;\n case 'read-only':\n throw new WritableSessionStoreRequiredError();\n }\n }\n}\n\n/**\n * Decides what to do with the stored session, before any effect runs. Pure,\n * so every combination of store, trigger and clock is a plain table.\n *\n * - A read-only store never refreshes. After a 401 it answers `null` only for\n * the very token that was just refused, which tells the renewal middleware\n * not to replay with it; a store that has moved on since hands its token\n * back.\n * - A writable store refreshes when the token is inside its buffer, or after\n * a 401 for the token it holds. A session with no refresh token whose token\n * still works is served as it is: nothing to renew with, but nothing lost\n * yet either, so the verdict waits for it to actually expire.\n */\nexport function classifyAccessToken({\n session,\n store,\n trigger,\n now,\n buffer,\n}: {\n session: OAuthSession | null;\n store: SessionStoreAccess;\n trigger: TokenTrigger;\n now: EpochMillis;\n buffer: Millis;\n}): AccessTokenClassification {\n if (session === null) {\n return { type: 'no-session' };\n }\n const token = session.accessToken;\n const stale = isExpired({\n expiresAt: EpochMillis.fromDate(token.expiresAt),\n now,\n buffer,\n });\n switch (store.type) {\n case 'read-only':\n return classifyReadOnly(token, trigger, stale);\n case 'writable': {\n if (!needsRefresh(token, trigger, stale)) {\n return { type: 'serve', token };\n }\n const fallback = fallbackToken(token, trigger, now);\n return session.refreshToken === undefined && fallback !== null\n ? { type: 'serve', token: fallback }\n : {\n type: 'refresh',\n session,\n setSession: store.setSession,\n fallback,\n };\n }\n }\n}\n\nfunction classifyReadOnly(\n token: OAuthAccessToken,\n trigger: TokenTrigger,\n stale: boolean\n): AccessTokenClassification {\n switch (trigger.type) {\n case 'after-401':\n return isRejectedToken(token, trigger.rejected)\n ? { type: 'refuse' }\n : { type: 'serve', token };\n case 'before-request':\n return stale ? { type: 'serve-stale', token } : { type: 'serve', token };\n }\n}\n\nfunction sessionStoreAccess(sessionStore: SessionStore): SessionStoreAccess {\n const setSession = sessionStore.setSession?.bind(sessionStore);\n return setSession === undefined\n ? { type: 'read-only' }\n : { type: 'writable', setSession };\n}\n\nfunction needsRefresh(\n token: OAuthAccessToken,\n trigger: TokenTrigger,\n stale: boolean\n): boolean {\n switch (trigger.type) {\n case 'before-request':\n return stale;\n case 'after-401':\n // The store may already hold a newer token than the refused one - a\n // refresh earlier in this same request - and that one is still good.\n return (\n trigger.rejected.type === 'no-user-token' ||\n isRejectedToken(token, trigger.rejected) ||\n stale\n );\n }\n}\n\n/**\n * The token to serve when a refresh cannot produce a new one: the current\n * one, as long as it has not actually expired - it is inside the early-refresh\n * buffer, not past it - and the API has not refused it. The backend keeps a\n * superseded access token valid until its own expiry, so serving it when the\n * refresh fails costs nothing.\n */\nfunction fallbackToken(\n token: OAuthAccessToken,\n trigger: TokenTrigger,\n now: EpochMillis\n): OAuthAccessToken | null {\n const unexpired = !isExpired({\n expiresAt: EpochMillis.fromDate(token.expiresAt),\n now,\n });\n switch (trigger.type) {\n case 'after-401':\n return unexpired && !isRejectedToken(token, trigger.rejected)\n ? token\n : null;\n case 'before-request':\n return unexpired ? token : null;\n }\n}\n\n/**\n * RFC 6749 §5.2's answer for a refresh token that is invalid, expired, revoked\n * or - under rotation - already used. CoinList's server does not say which.\n */\nfunction isInvalidGrant(error: unknown): boolean {\n if (!(error instanceof HttpError) || error.response.status !== 400) {\n return false;\n }\n const body: unknown = error.response.body;\n return (\n typeof body === 'object' &&\n body !== null &&\n 'error' in body &&\n body.error === 'invalid_grant'\n );\n}\n","import type { ServerNamespaceContext } from '@/server/core/server-namespace-context';\nimport * as offersApi from '@/universal/api/frontline/offers';\nimport type { OffersNamespace } from '@/universal/core/offers/offers-namespace';\nimport {\n type InternalLogger,\n internalLogger,\n} from '@/universal/core/shared/observability/internal-logger';\nimport type { ClientCredentialsOAuth } from '@/universal/types/oauth-session';\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 * The server-side offers namespace: the same reads as the shared\n * {@link OffersNamespace}, each additionally callable with an app-level token\n * from `ServerAuthNamespace.clientCredentials` instead of a user session.\n *\n * When a user session exists it takes precedence and `clientCreds` acts as a\n * fallback. Without either, the reads throw {@link NotAuthenticatedError}.\n */\nexport interface ServerOffersNamespace extends OffersNamespace {\n /** @param clientCreds app-level token to read without a user session. */\n list(clientCreds?: ClientCredentialsOAuth): Promise<Offer[]>;\n\n /** @param clientCreds app-level token to read without a user session. */\n listPage(\n params: PaginationParams,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<PaginatedResponse<Offer>>;\n\n /** @param clientCreds app-level token to read without a user session. */\n get(id: OfferId, clientCreds?: ClientCredentialsOAuth): Promise<OfferDetail>;\n}\n\nexport class ServerOffersNamespaceImpl implements ServerOffersNamespace {\n private readonly log: InternalLogger;\n\n constructor(private readonly ctx: ServerNamespaceContext) {\n this.log = internalLogger(ctx.logger, 'OFFERS');\n }\n\n async list(clientCreds?: ClientCredentialsOAuth): Promise<Offer[]> {\n return this.log.wrap('list', clientCreds, async () => {\n await this.ctx.ensureAuthenticated(clientCreds);\n return offersApi.fetchOffers(this.ctx.api, clientCreds);\n });\n }\n\n async listPage(\n params: PaginationParams,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<PaginatedResponse<Offer>> {\n return this.log.wrap('listPage', { params, clientCreds }, async () => {\n await this.ctx.ensureAuthenticated(clientCreds);\n return offersApi.fetchOffersPage(this.ctx.api, params, clientCreds);\n });\n }\n\n async get(\n id: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<OfferDetail> {\n return this.log.wrap('get', { id, clientCreds }, async () => {\n await this.ctx.ensureAuthenticated(clientCreds);\n return offersApi.fetchOfferDetails(this.ctx.api, id, clientCreds);\n });\n }\n}\n","import type { ServerNamespaceContext } from '@/server/core/server-namespace-context';\nimport * as requirementsApi from '@/universal/api/frontline/requirements';\nimport {\n type RequirementsNamespace,\n RequirementsNamespaceImpl,\n} from '@/universal/core/requirements/requirements-namespace';\nimport type { UniversalNamespaceContext } from '@/universal/core/shared/namespace-context';\nimport type { ClientCredentialsOAuth } from '@/universal/types/oauth-session';\nimport type { OfferId } from '@/universal/types/offer';\nimport type { OfferOptionId } from '@/universal/types/offer-detail';\nimport type { Requirement } from '@/universal/types/requirement';\n\n/**\n * The server-side requirements namespace: the same surface as the shared\n * {@link RequirementsNamespace}, except {@link forOffer} — the offer's\n * requirement definitions are app-level data, so it is additionally callable\n * with a token from `ServerAuthNamespace.clientCredentials`.\n *\n * Everything else is per-user data and needs a user session. `handle` is\n * client-only: there is no browser tab to open on a server.\n */\nexport interface ServerRequirementsNamespace extends RequirementsNamespace {\n /** @param clientCreds app-level token to read without a user session. */\n forOffer(\n offerId: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<Record<OfferOptionId, Requirement[]>>;\n}\n\nexport class ServerRequirementsNamespaceImpl\n extends RequirementsNamespaceImpl\n implements ServerRequirementsNamespace\n{\n constructor(\n private readonly serverCtx: UniversalNamespaceContext &\n ServerNamespaceContext\n ) {\n super(serverCtx);\n }\n\n async forOffer(\n offerId: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<Record<OfferOptionId, Requirement[]>> {\n return this.log.wrap('forOffer', { offerId, clientCreds }, async () => {\n await this.serverCtx.ensureAuthenticated(clientCreds);\n return requirementsApi.fetchOfferRequirements(\n this.serverCtx.api,\n offerId,\n clientCreds\n );\n });\n }\n}\n","import { ApiClient } from '@/server/core/api/api-client';\nimport type {\n ServerAuthNamespace,\n SessionStore,\n} from '@/server/core/server-auth-namespace';\nimport { ServerAuthNamespaceImpl } from '@/server/core/server-auth-namespace-impl';\nimport type { ServerNamespaceContext } from '@/server/core/server-namespace-context';\nimport {\n type ServerOffersNamespace,\n ServerOffersNamespaceImpl,\n} from '@/server/core/server-offers-namespace';\nimport {\n type ServerRequirementsNamespace,\n ServerRequirementsNamespaceImpl,\n} from '@/server/core/server-requirements-namespace';\nimport { processRefreshCoordinator } from '@/server/core/session-refresh';\nimport {\n API_VERSION,\n PUBLIC_API_BASE_URL,\n} from '@/universal/api/frontline/config';\nimport { NABU_BASE_URL } from '@/universal/api/nabu/config';\nimport { publicClients } from '@/universal/api/rpc/public-clients';\nimport {\n type CentrifugeNamespace,\n CentrifugeNamespaceImpl,\n} from '@/universal/core/checkout/centrifuge/centrifuge-namespace';\nimport {\n type CoinListTokenSaleNamespace,\n CoinListTokenSaleNamespaceImpl,\n} from '@/universal/core/checkout/coin-list/token-sale-namespace';\nimport {\n type OndoNamespace,\n OndoNamespaceImpl,\n} from '@/universal/core/checkout/ondo/ondo-namespace';\nimport {\n type SuperstateSwapNamespace,\n SuperstateSwapNamespaceImpl,\n} from '@/universal/core/checkout/superstate/swap-namespace';\nimport {\n type Erc20Namespace,\n Erc20NamespaceImpl,\n} from '@/universal/core/shared/blockchain/erc20/erc20-namespace';\nimport { systemTime } from '@/universal/core/shared/effects';\nimport type { UniversalNamespaceContext } from '@/universal/core/shared/namespace-context';\nimport {\n type TokensNamespace,\n TokensNamespaceImpl,\n} from '@/universal/core/tokens/tokens-namespace';\nimport {\n type WalletsNamespace,\n WalletsNamespaceImpl,\n} from '@/universal/core/wallets/wallets-namespace';\nimport type { Config } from '@/universal/types/config';\nimport { NotAuthenticatedError } from '@/universal/types/errors';\nimport type { ClientSecret } from '@/universal/types/oauth';\nimport type { ClientCredentialsOAuth } from '@/universal/types/oauth-session';\nimport { Seconds } from '@/universal/types/time';\n\n/** Buffer before expiry to consider token expired for refresh. */\nconst ACCESS_TOKEN_EXPIRY_BUFFER: Seconds = Seconds.of(60);\n\nexport interface ServerConfig extends Config {\n readonly clientSecret: ClientSecret;\n readonly sessionStore: SessionStore;\n /**\n * How many seconds before its expiry an access token is refreshed. Defaults\n * to 60. Refreshing early moves the work to a single request while the\n * current token still works, and a refresh that fails inside this window\n * keeps serving the current token rather than failing the request.\n *\n * Build it with `Seconds.of(n)`, which throws a `ValidationError` unless\n * `n` is a non-negative integer.\n */\n readonly accessTokenExpiryBufferSeconds?: Seconds;\n /**\n * Whether the SDK throws where it would otherwise recover and log a\n * `'warn'` line. Defaults to `false`.\n *\n * Today it governs one thing: a failed token revoke in `auth.logout()`. By\n * default the session is cleared regardless; with `strict` the failure is\n * rethrown and the session is left untouched.\n */\n readonly strict?: boolean;\n}\n\n/**\n * Server-side CoinList SDK client.\n *\n * Functionality is grouped into namespaces — `coinlist.auth.getAccessToken()`,\n * `coinlist.offers.list()`, `coinlist.requirements.statuses(offerId)`.\n *\n * Operates in one of two modes depending on whether {@link SessionStore}\n * includes a `setSession` implementation:\n *\n * - **Writable store** (`setSession` provided) — full functionality: token\n * refresh, `auth.completeOAuth`, and `auth.logout` all work normally.\n *\n * - **Read-only store** (no `setSession`) — token refresh is skipped entirely,\n * meaning no network call is made and no refresh token is consumed.\n * `auth.completeOAuth` and `auth.logout` throw\n * {@link WritableSessionStoreRequiredError}. `auth.getAccessToken` may\n * return an expired token (see its docs). Use this mode in execution\n * contexts that can read the session but cannot write it back, such as\n * Next.js Server Components.\n *\n * Unless a namespace says otherwise, its methods require a user session and\n * throw {@link NotAuthenticatedError} when there is none. When the SDK could\n * not find out whether there is one - CoinList's token endpoint failing, or\n * the network - they throw that underlying error instead: see\n * {@link ServerAuthNamespace.getAccessToken}.\n */\nexport interface CoinListServer {\n /**\n * OAuth session lifecycle: exchanging an authorization code, serving a valid\n * access token to the browser, minting app-level tokens, and logging out —\n * e.g. `coinlist.auth.getAccessToken()`.\n */\n readonly auth: ServerAuthNamespace;\n\n /**\n * The offer catalogue — e.g. `coinlist.offers.list()`. Every read also\n * accepts an app-level token from {@link ServerAuthNamespace.clientCredentials}\n * so it can run without a user session.\n */\n readonly offers: ServerOffersNamespace;\n\n /**\n * Offer requirements and the operations that satisfy them — e.g.\n * `coinlist.requirements.statuses(offerId)`. `forOffer` also accepts an\n * app-level token; the rest is per-user data and needs a user session.\n */\n readonly requirements: ServerRequirementsNamespace;\n\n /**\n * The user's external wallets: ownership proofs and offer-option bindings —\n * e.g. `coinlist.wallets.list({ offerId, offerOptionId })`.\n */\n readonly wallets: WalletsNamespace;\n\n /**\n * Generic ERC-20 reads (token allowance and balance) — e.g.\n * `coinlist.erc20.getBalance({ ... })`.\n */\n readonly erc20: Erc20Namespace;\n\n /**\n * Token-sale operations: listing, reading, and recording participations —\n * e.g. `coinlist.tokenSale.list()`. The on-chain `execute`\n * flow is client-only and is not exposed here.\n */\n readonly tokenSale: CoinListTokenSaleNamespace;\n\n /**\n * On-chain swap operations: quoting a swap, reading swap-contract state, and\n * allow-listing a proven wallet — e.g.\n * `coinlist.superstate.getOutputToken({ contractAddress, chain })`.\n */\n readonly superstate: SuperstateSwapNamespace;\n\n /**\n * Ondo swap reads: trading status and quoting. Both are free to poll.\n *\n * No CoinList fee is applied to a quote; see {@link OndoNamespace}.\n *\n * This is Ondo's whole surface for now — the wallet-driven half arrives with\n * ENG-1680 and will be client-only, since it needs a wallet to sign with.\n */\n readonly ondo: OndoNamespace;\n\n /**\n * Centrifuge vault reads: the vault-wide ordering gate, and each wallet's\n * position. The status read is free to poll.\n */\n readonly centrifuge: CentrifugeNamespace;\n\n /**\n * Token display metadata (name, symbol, decimals, logos) from CoinList's\n * public token registry, keyed by chain + contract address — e.g.\n * `coinlist.tokens.get({ chain, address })` for an entry of\n * `Offer.tokens`, or `coinlist.tokens.list()` for the whole\n * catalogue in one request.\n *\n * Public and unauthenticated: unlike the other namespaces, its methods never\n * require a session, so it also works with a read-only store.\n */\n readonly tokens: TokensNamespace;\n}\n\nclass CoinListServerImpl implements CoinListServer {\n private readonly api: ApiClient;\n readonly auth: ServerAuthNamespace;\n readonly offers: ServerOffersNamespace;\n readonly requirements: ServerRequirementsNamespace;\n readonly wallets: WalletsNamespace;\n readonly erc20: Erc20Namespace;\n readonly tokenSale: CoinListTokenSaleNamespace;\n readonly superstate: SuperstateSwapNamespace;\n readonly ondo: OndoNamespace;\n readonly centrifuge: CentrifugeNamespace;\n readonly tokens: TokensNamespace;\n\n constructor(_config: ServerConfig) {\n const logger = _config.logger ?? null;\n this.api = new ApiClient(\n {\n baseUrl: _config.baseUrl ?? PUBLIC_API_BASE_URL,\n xApiVersion: API_VERSION,\n },\n (trigger) => auth.tokenFor(trigger),\n logger\n );\n const auth = new ServerAuthNamespaceImpl(this.api, {\n ..._config,\n accessTokenExpiryBufferSeconds:\n _config.accessTokenExpiryBufferSeconds ?? ACCESS_TOKEN_EXPIRY_BUFFER,\n strict: _config.strict ?? false,\n refreshes: processRefreshCoordinator,\n ...systemTime,\n });\n this.auth = auth;\n\n const ctx: UniversalNamespaceContext & ServerNamespaceContext = {\n api: this.api,\n rpc: publicClients(this.api, _config.rpc, _config.logger ?? null),\n logger,\n ensureUserAuthenticated: () => this.ensureAuthenticated(undefined),\n ensureAuthenticated: (clientCreds) =>\n this.ensureAuthenticated(clientCreds),\n };\n this.offers = new ServerOffersNamespaceImpl(ctx);\n this.requirements = new ServerRequirementsNamespaceImpl(ctx);\n this.wallets = new WalletsNamespaceImpl(ctx);\n this.erc20 = new Erc20NamespaceImpl(ctx);\n this.tokenSale = new CoinListTokenSaleNamespaceImpl(ctx);\n this.superstate = new SuperstateSwapNamespaceImpl(ctx);\n this.ondo = new OndoNamespaceImpl(ctx);\n this.centrifuge = new CentrifugeNamespaceImpl(ctx);\n this.tokens = new TokensNamespaceImpl(\n _config.tokensBaseUrl ?? NABU_BASE_URL,\n logger\n );\n }\n\n /**\n * An app-level token authenticates a request on its own; without one the\n * caller needs a user session.\n */\n private async ensureAuthenticated(\n clientCreds: ClientCredentialsOAuth | undefined\n ): Promise<void> {\n if (clientCreds) return;\n const token = await this.auth.getAccessToken();\n if (token === null) {\n throw new NotAuthenticatedError();\n }\n }\n}\n\nexport function createCoinListServer(config: ServerConfig): CoinListServer {\n return new CoinListServerImpl(config);\n}\n","import { pino } from 'pino';\nimport {\n loggerOverPino,\n PINO_LEVEL,\n SDK_LOGGER_NAME,\n} from '@/universal/core/shared/observability/pino-logger';\nimport type { Logger, PinoLoggerOptions } from '@/universal/types/logger';\n\n/**\n * A {@link Logger} that writes newline-delimited JSON to stdout through pino.\n *\n * ```ts\n * createCoinListServer({\n * ...config,\n * logger: pinoServerLogger({ isDev: false, level: 'info' }),\n * });\n * ```\n *\n * The Node counterpart of {@link pinoClientLogger}, for a route handler, a BFF\n * or SSR. One line per record, which is what every log aggregator ingests\n * without configuration.\n *\n * ## What it deliberately does not do\n *\n * **No transport, and no `pino-pretty`.** A pino transport runs on a worker\n * thread through `thread-stream`, which every bundler has to be told about\n * (`serverExternalPackages` in Next) and which edge runtimes cannot run at\n * all. Pretty-printing is a pipe the host owns rather than a dependency the\n * SDK takes on their behalf:\n *\n * ```sh\n * node server.js | npx pino-pretty\n * ```\n *\n * **No environment-dependent formatting.** `isDev` decides which levels are\n * legal and nothing else, so a development record and a production record are\n * the same shape - what you debug is what you ship.\n *\n * If you already run pino, or want transports, redaction or `destination`,\n * implement {@link Logger} yourself: it is four methods over an event, and\n * `@opentelemetry/instrumentation-pino` patches the pino module rather than an\n * instance, so trace correlation reaches this logger either way.\n *\n * ## Production\n *\n * **Not advised in production**, the same as everywhere else this seam is\n * documented: the SDK's default answer is to leave {@link Config.logger}\n * undefined outside development, staging and incident reproduction. A backend\n * is the least bad place to disregard that - the records go to your process\n * and your sink rather than to a page the end user can read - but it is still\n * a disregard, and the retention and access questions become yours.\n *\n * If you run one, `isDev: false` restricts you to `'error'`, `'warn'` and\n * `'info'` - `{ isDev: false, level: 'debug' }` does not compile, see\n * {@link PinoLoggerOptions}. Those levels are redacted by construction, which\n * is a mechanism the SDK holds itself to; whether that clears your bar is a\n * judgement the SDK is not in a position to make for you.\n *\n * Holds no module-level state: every call builds its own pino instance, and\n * the level is fixed at construction, so two loggers never interfere. Fixed is\n * this implementation's choice, not the seam's - the SDK re-reads\n * {@link Logger.level} before every log call - so if you want a level you can\n * change at runtime, implement {@link Logger} over your own pino instance\n * rather than calling this.\n */\nexport function pinoServerLogger(options: PinoLoggerOptions): Logger {\n return loggerOverPino(\n // `name` as a child binding rather than pino's `name` option: the option\n // is honoured by pino's node build and silently dropped by its browser\n // build, so a binding is the only spelling that identifies the SDK in\n // both environments.\n pino({\n level: PINO_LEVEL[options.level],\n }).child({ name: SDK_LOGGER_NAME }),\n options.level\n );\n}\n","import type { AuthorizationCode, CodeVerifier } from '@/universal/types/oauth';\nimport type {\n ClientCredentialsOAuth,\n OAuthAccessToken,\n OAuthSession,\n} from '@/universal/types/oauth-session';\n\nexport interface SessionStore {\n getSession(): Promise<OAuthSession | null>;\n /**\n * Persists or clears the OAuth session.\n *\n * **Omit this method to create a read-only store.** When absent, the SDK\n * skips token refresh entirely — no network call is made and no refresh\n * token is consumed. This is the correct approach for contexts that can read\n * the session but cannot write it back, such as Next.js Server Components.\n *\n * ⚠️ Do **not** implement this as a no-op (`async () => {}`). If the method\n * is present, the SDK assumes writes succeed: it will fire a token refresh\n * network call, consume the refresh token, then invoke `setSession` — which\n * would silently discard the new session and leave the browser holding an\n * invalidated refresh token. Simply **omit** `setSession` to prevent any\n * refresh from being attempted.\n *\n * {@link ServerAuthNamespace.completeOAuth} and\n * {@link ServerAuthNamespace.logout} always require a writable store and\n * throw {@link WritableSessionStoreRequiredError} if `setSession` is absent.\n */\n setSession?(session: OAuthSession | null): Promise<void>;\n}\n\n/**\n * The empty session store: holds no session, reads nothing, never writes.\n *\n * For server contexts that act without a user — the client-credentials grant\n * (`auth.clientCredentials`) and the public token registry (`tokens`) read\n * nothing from the store, so hosts building those paths need no store of\n * their own.\n *\n * `setSession` is deliberately absent, not a no-op — see\n * {@link SessionStore.setSession} for why a no-op is a bug.\n */\nexport function emptySessionStore(): SessionStore {\n return { getSession: async () => null };\n}\n\n/** The PKCE pair `CoinListClient#auth.completeOAuth` hands to the backend. */\nexport interface CompleteOAuthParams {\n /** The short-lived authorization code parsed out of the redirect URL. */\n code: AuthorizationCode;\n /** The `code_verifier` the browser generated when starting the flow. */\n codeVerifier: CodeVerifier;\n}\n\n/**\n * OAuth session lifecycle on your backend: exchanging authorization codes,\n * keeping the access token fresh, minting app-level tokens, and logging out.\n *\n * This namespace is server-only: every method needs the `clientSecret` and the\n * {@link SessionStore}, neither of which may exist in a browser.\n */\nexport interface ServerAuthNamespace {\n /**\n * Exchanges an authorization code from `CoinListClient#auth.completeOAuth`\n * for an OAuth session and persists it via {@link SessionStore.setSession}.\n *\n * The exchange is never retried automatically: an authorization code is\n * single-use, so a retry of an attempt that reached the server would fail\n * anyway, and could mask why the first one did.\n *\n * @throws {@link WritableSessionStoreRequiredError} if the session store\n * does not provide `setSession`.\n * @throws {@link HttpError} if CoinList refuses the exchange.\n */\n completeOAuth(params: CompleteOAuthParams): Promise<OAuthSession>;\n\n /**\n * Returns a valid access token for the current session, refreshing it if it\n * is expired or near expiry. This is what you serve to\n * {@link ClientConfig.getAccessToken}: answer `null` with \"no session\"\n * (e.g. a 204) and a throw with a failure (e.g. a 503), never the other way\n * round - the browser signs the user out on the first and not the second.\n *\n * **Writable store**: if the token is expired, the SDK exchanges the refresh\n * token for a new session, persists it, and returns the fresh access token.\n * Concurrent calls for the same session in this process share one exchange.\n *\n * A rejected refresh token (`400 invalid_grant`) is only a *suspicion* at\n * first, because the backend answers \"rotated a moment ago by a concurrent\n * request\" and \"expired or revoked\" identically: the SDK writes nothing,\n * so it cannot overwrite the session the winning request just stored, and\n * returns `null` - or the current access token, if that has not actually\n * expired yet. Only a second rejection of the same refresh token, 5 to 60\n * seconds later in this process, is the verdict: the session is cleared via\n * {@link SessionStore.setSession} and {@link Config.onSessionExpired} is\n * called. A browser confirming through your endpoint asks again after one\n * second, so it signs out on its own verdict and not on this one.\n *\n * **Read-only store** (no `setSession`): refresh is skipped entirely. The\n * stored token is returned as-is, even if it is expired — a non-null return\n * value does **not** guarantee the token is accepted by the API. Before\n * making API calls, check `token.expiresAt > new Date()`. Use a writable\n * store (e.g. in a Route Handler) when you need the SDK to renew the session\n * automatically.\n *\n * @returns the access token, or `null` if there is no session, or the\n * session was rejected (suspected or confirmed).\n * @throws the underlying error - an {@link HttpError} for a 5xx or\n * `invalid_client`, the `fetch` error for a network failure - when the SDK\n * could not find out whether a session exists and the current access token\n * has already expired. The session is left untouched. **Never treat this as\n * logged out**: it means \"try again\", and during an outage treating it as\n * logged out would sign out every user at once.\n */\n getAccessToken(): Promise<OAuthAccessToken | null>;\n\n /**\n * Obtains an app-level access token via the OAuth 2.0 `client_credentials`\n * grant (RFC 6749 §4.4). No user is involved: the token authenticates the\n * partner application itself and only grants access to app-level resources\n * such as offers and offer requirements.\n *\n * The token is **not** persisted to the {@link SessionStore} and has no\n * refresh token. It expires at `expiresAt`; once expired, call this method\n * again to obtain a fresh token — the SDK does not renew it automatically.\n *\n * Pass the result to `ServerOffersNamespace` or\n * `ServerRequirementsNamespace.forOffer` to read without a user session.\n */\n clientCredentials(): Promise<ClientCredentialsOAuth>;\n\n /**\n * Revokes the current token via `POST /oauth/revoke` and clears the session.\n * Does nothing when there is no session.\n *\n * The revoke is best-effort: if it fails for any reason - an\n * {@link HttpError}, a network failure, anything else - the failure is\n * logged at `'warn'` and the session is cleared regardless, because the\n * cleared session is what logs the user out. Set `strict` on the server\n * config to rethrow it instead.\n *\n * @throws {@link WritableSessionStoreRequiredError} if the session store does\n * not provide `setSession`.\n * @throws whatever the {@link SessionStore} throws, in either mode. A failed\n * `setSession(null)` means the session was not cleared.\n * @throws the revoke failure, only when `strict` is set. The session is then\n * left untouched, so the call can be retried.\n */\n logout(): Promise<void>;\n}\n"],"mappings":"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","names":[]}
@@ -1188,16 +1188,6 @@ type GetCentrifugeVaultStatusParams = {
1188
1188
  */
1189
1189
  chain: EthereumChain;
1190
1190
  };
1191
- type GetCentrifugeVaultQuoteParams = GetCentrifugeVaultStatusParams & {
1192
- /**
1193
- * Amount to price, in the base units of the funding token. Must be greater
1194
- * than zero. No fee is deducted; this product charges none.
1195
- *
1196
- * Sized by `amount` alone: the quote is a free read of one spend, not a
1197
- * dollar figure converted later.
1198
- */
1199
- amount: BlockchainAmount;
1200
- };
1201
1191
  /**
1202
1192
  * One wallet on one vault: what every position read and every claim is keyed
1203
1193
  * by. There is no order id - a wallet has one pooled position per vault per
@@ -1309,7 +1299,7 @@ type InternalDebugEvent = {
1309
1299
  * then, and the user claims the resulting shares in a second transaction. So
1310
1300
  * every share figure before a fill is an estimate.
1311
1301
  *
1312
- * The three GETs are free to poll. The two POSTs return calldata to broadcast:
1302
+ * The GETs are free to poll. The two POSTs return calldata to broadcast:
1313
1303
  * `invest` is signed for one wallet and called once on confirmation, `claim`
1314
1304
  * collects everything claimable and carries no amount.
1315
1305
  */
@@ -1318,8 +1308,8 @@ type InternalDebugEvent = {
1318
1308
  * branch on every value. `vault_unlinked` is terminal for the offer's proxy on
1319
1309
  * that chain; the rest clear on their own or by hand.
1320
1310
  *
1321
- * `below_minimum` is the one reason that depends on an amount, so only a quote
1322
- * can report it - see {@link CentrifugeVaultReasonDto}.
1311
+ * `below_minimum` depends on an amount, so a vault status never reports it —
1312
+ * see {@link CentrifugeVaultReasonDto}.
1323
1313
  */
1324
1314
  type CentrifugeOrderReasonDto = 'vault_unlinked' | 'vault_halted' | 'proxy_not_permissioned' | 'price_unavailable' | 'below_minimum';
1325
1315
  /** What a vault status can report: every reason but the amount-dependent one. */
@@ -1330,26 +1320,6 @@ type CentrifugeVaultReasonDto = Exclude<CentrifugeOrderReasonDto, 'below_minimum
1330
1320
  * while earlier shares stay claimed.
1331
1321
  */
1332
1322
  type CentrifugePositionStatusDto = 'none' | 'pending' | 'partially_filled' | 'filled' | 'claimed';
1333
- /** `GET /v1/centrifuge/vault/quote?symbol&chain&amount`. Indicative only. */
1334
- type CentrifugeVaultQuoteDto = {
1335
- object: 'centrifuge_vault_quote';
1336
- /** The vault's share token symbol, e.g. `deJTRSY`. */
1337
- symbol: string;
1338
- chain: string;
1339
- /** The amount priced, echoed back, in `spend_input_decimals`. No fee. */
1340
- spend_input_amount: string;
1341
- spend_input_decimals: number;
1342
- /** An estimate, not a floor: the fill can price either side of it. */
1343
- estimated_shares: string;
1344
- share_decimals: number;
1345
- /** Funding token per whole share. Null when the vault has no price. */
1346
- indicative_price: string | null;
1347
- /** In `spend_input_decimals`. Null when the offer sets no minimum. */
1348
- minimum_input_amount: string | null;
1349
- can_order: boolean;
1350
- /** Empty exactly when `can_order` is true. */
1351
- reasons: CentrifugeOrderReasonDto[];
1352
- };
1353
1323
  /**
1354
1324
  * `GET /v1/centrifuge/vault/status?symbol&chain`. The vault's own state, read
1355
1325
  * from the chain on every call; the gate to poll, because Centrifuge announces
@@ -1394,9 +1364,6 @@ type CentrifugeVaultBuyDto = CentrifugeBroadcastDto & {
1394
1364
  /** Exactly what the approval has to cover: no fee comes off it. */
1395
1365
  spend_input_amount: string;
1396
1366
  spend_input_decimals: number;
1397
- /** Not a quantity purchased: the fill prices later. */
1398
- estimated_shares: string;
1399
- share_decimals: number;
1400
1367
  };
1401
1368
  /**
1402
1369
  * `GET /v1/centrifuge/vault/position?symbol&chain&wallet_address`. The six
@@ -1514,31 +1481,6 @@ type CentrifugeOrderability<R extends CentrifugeOrderReason> = {
1514
1481
  type: 'blocked';
1515
1482
  reasons: [R, ...R[]];
1516
1483
  };
1517
- /**
1518
- * What a deposit of a given size would buy, and whether it can be placed.
1519
- *
1520
- * **Indicative only.** The price is read from the vault now and the fund
1521
- * prices the fulfilment on its own epoch, so neither {@link indicativePrice}
1522
- * nor {@link estimatedShares} is a commitment. Free to poll while the user
1523
- * sizes a deposit.
1524
- */
1525
- type CentrifugeVaultQuote = {
1526
- chain: EthereumChain;
1527
- /** The vault's share token symbol, e.g. `deJTRSY`. */
1528
- symbol: AssetSymbol;
1529
- /** The amount priced, in the funding token's decimals. No fee is taken. */
1530
- spendInputAmount: BlockchainAmount;
1531
- /** Shares at the indicative price, in the share token's decimals. */
1532
- estimatedShares: BlockchainAmount;
1533
- /** Funding token per whole share. Null when the vault publishes no price. */
1534
- indicativePrice: DecimalString | null;
1535
- /** Null when the offer sets no minimum. Same decimals as the spend. */
1536
- minimumInputAmount: BlockchainAmount | null;
1537
- orderability: CentrifugeOrderability<CentrifugeOrderReason>;
1538
- };
1539
- declare const CentrifugeVaultQuote: {
1540
- fromDto: (dto: CentrifugeVaultQuoteDto) => CentrifugeVaultQuote;
1541
- };
1542
1484
  /**
1543
1485
  * A vault's own state, independent of any wallet or amount: the gate to poll.
1544
1486
  * Centrifuge announces none of these changes, and an unlinked vault leaves the
@@ -1563,7 +1505,7 @@ type CentrifugeVaultStatus = {
1563
1505
  membershipValidUntil: Date | null;
1564
1506
  /** The proxy halted itself after a force-cancel; cleared only by hand. */
1565
1507
  halted: boolean;
1566
- /** A stale value is the signal that a quote's price is old. */
1508
+ /** When the vault last published a price. A stale value means the price is old. */
1567
1509
  priceUpdatedAt: Date | null;
1568
1510
  orderability: CentrifugeOrderability<CentrifugeVaultReason>;
1569
1511
  /** When the chain was read: how old everything above is. */
@@ -1590,14 +1532,11 @@ type CentrifugeVaultTransactionCore = {
1590
1532
  *
1591
1533
  * **Broadcasting it buys nothing yet.** It places a request in the vault's
1592
1534
  * queue; the fund fulfils it later at a price set then, and the user comes
1593
- * back to claim. {@link estimatedShares} is therefore never a quantity
1594
- * purchased. Single-use: built once on confirmation, never on a timer.
1535
+ * back to claim. Single-use: built once on confirmation, never on a timer.
1595
1536
  */
1596
1537
  type CentrifugeVaultBuyTransaction = CentrifugeVaultTransactionCore & {
1597
1538
  /** Exactly what the approval has to cover: no fee comes off it. */
1598
1539
  spendInputAmount: BlockchainAmount;
1599
- /** At the price read when the calldata was built. An estimate, not a floor. */
1600
- estimatedShares: BlockchainAmount;
1601
1540
  };
1602
1541
  declare const CentrifugeVaultBuyTransaction: {
1603
1542
  fromDto: (dto: CentrifugeVaultBuyDto) => CentrifugeVaultBuyTransaction;
@@ -1738,14 +1677,12 @@ declare const CentrifugeVaultClaimProceedsTransaction: {
1738
1677
  };
1739
1678
 
1740
1679
  /**
1741
- * Centrifuge vault reads and claims: an indicative quote, the vault-wide
1742
- * ordering gate, each wallet's position per direction, and the calldata that
1743
- * claims a filled request.
1680
+ * Centrifuge vault reads and claims: the vault-wide ordering gate, each
1681
+ * wallet's position per direction, and the calldata that claims a filled
1682
+ * request.
1744
1683
  *
1745
1684
  * The reads are free to poll - none spends an attestation, so a client may
1746
- * call them while the user sizes a deposit or waits on a fill. The quote has
1747
- * no expiry: it is indicative, the fund prices fulfilment later, and a
1748
- * countdown would imply a commitment the API does not make.
1685
+ * call them while the user sizes a deposit or waits on a fill.
1749
1686
  *
1750
1687
  * A vault trade settles in two legs: a request the fund fills on its own
1751
1688
  * schedule, then a claim. There is no order id - each wallet has one pooled
@@ -1761,7 +1698,6 @@ declare const CentrifugeVaultClaimProceedsTransaction: {
1761
1698
  * {@link NotAuthenticatedError} otherwise.
1762
1699
  */
1763
1700
  interface CentrifugeNamespace {
1764
- getQuote(params: GetCentrifugeVaultQuoteParams): Promise<CentrifugeVaultQuote>;
1765
1701
  getVaultStatus(params: GetCentrifugeVaultStatusParams): Promise<CentrifugeVaultStatus>;
1766
1702
  /** The wallet's invest position: funding queued, shares claimable. */
1767
1703
  getPosition(params: CentrifugeWalletParams): Promise<CentrifugeVaultPosition>;
@@ -1794,7 +1730,6 @@ declare class CentrifugeNamespaceImpl implements CentrifugeNamespace {
1794
1730
  private readonly ctx;
1795
1731
  protected readonly log: InternalLogger;
1796
1732
  constructor(ctx: UniversalNamespaceContext);
1797
- getQuote(params: GetCentrifugeVaultQuoteParams): Promise<CentrifugeVaultQuote>;
1798
1733
  getVaultStatus(params: GetCentrifugeVaultStatusParams): Promise<CentrifugeVaultStatus>;
1799
1734
  getPosition(params: CentrifugeWalletParams): Promise<CentrifugeVaultPosition>;
1800
1735
  getRedeemPosition(params: CentrifugeWalletParams): Promise<CentrifugeVaultRedeemPosition>;
@@ -4049,4 +3984,4 @@ declare function isExpired({ expiresAt, now, buffer, }: {
4049
3984
  buffer?: Millis;
4050
3985
  }): boolean;
4051
3986
 
4052
- export { AsyncOfferDetail as $, AuthorizationCode as A, BlockchainAmount as B, CodeVerifier as C, type OAuthAccessToken as D, type Erc20Namespace as E, type OffersNamespace as F, DecimalString as G, StablecoinSymbol as H, type Erc20Asset as I, OfferDetail as J, TokenMetadata as K, type Logger as L, type CentrifugeVaultOfferDetail as M, type OfferType as N, OfferVaultContract as O, Participation as P, type OndoSwapOfferDetail as Q, type RequirementsNamespace as R, type SuperstateSwapNamespace as S, type TokensNamespace as T, type UniversalNamespaceContext as U, type CoinListTokenSaleOfferDetail as V, type WalletError as W, type SuperstateSwapOfferDetail as X, CentrifugeVaultLegId as Y, Seconds as Z, type AsyncOfferType as _, EthereumChain as a, type CentrifugeVaultOffer as a$, Offer as a0, RequirementStatusInfo as a1, type RequirementType as a2, type RequirementStatusValue as a3, OfferOptionAddress as a4, RequirementId as a5, type KycLevelName as a6, DocumentSubmission as a7, type WalletChallengeType as a8, type PinoLoggerOptions as a9, type ProductionLogLevel as aA, RedactedWalletError as aB, type RequestId as aC, type RpcFailureReason as aD, type SafeEvent as aE, type SafeFields as aF, type UnredactedFields as aG, OAuthSession as aH, ClientCredentialsOAuth as aI, type Sender as aJ, PaginationParams as aK, PaginatedResponse as aL, ClientSecret as aM, OfferToken as aN, SwapStatus as aO, type Uint256 as aP, KnownAssetSymbol as aQ, type Newtype as aR, type AllowWalletParams as aS, AllowWalletResponse as aT, Asset as aU, AssetCode as aV, Blockchain as aW, type BuildOndoSwapParamsCore as aX, type CentrifugePositionStatus as aY, CentrifugeVaultClaimProceedsTransaction as aZ, CentrifugeVaultClaimTransaction as a_, AssetDecimals as aa, type BuildCentrifugeVaultBuyParams as ab, CentrifugeVaultQuote as ac, type CentrifugeOrderReason as ad, type CentrifugeOrderability as ae, OndoTradingStatus as af, type BuildOndoBuyParams as ag, type BuildOndoSellParams as ah, OndoQuote as ai, type OndoQuoteSize as aj, type TokenIdentifier as ak, CentrifugeVaultLeg as al, OfferOptionAddressId as am, CentrifugeVaultStatus as an, TokenLogo as ao, type DebugEvent as ap, type FrontlineEventId as aq, HttpError as ar, type HttpResponse as as, KycToken as at, type LogBinding as au, type LogBindings as av, type LogCause as aw, type LogLevel as ax, type LogScope as ay, type LogValue as az, EvmContractAddress as b, type RequirementActionNeededReason as b$, CentrifugeVaultPosition as b0, type CentrifugeVaultReason as b1, CentrifugeVaultRedeemPosition as b2, type CentrifugeVaultTransactionCore as b3, type CentrifugeWalletParams as b4, Chain as b5, ClientId as b6, CodeChallenge as b7, type CoinListTokenSaleOffer as b8, ConnectExternalWalletParams as b9, Milestone as bA, Millis as bB, OAuthRefreshToken as bC, type OfferBase as bD, type OfferDetailBase as bE, OfferEditorial as bF, OfferOption as bG, OfferOptionSlug as bH, OfferSlug as bI, OffersNamespaceImpl as bJ, type OndoQuoteDuration as bK, type OndoSellOutcome as bL, type OndoSwapOffer as bM, PKCEState as bN, type PaginatedResponseDto as bO, ParticipationId as bP, type ParticipationStatus as bQ, ParticipationsPaginationParams as bR, PauseFlags as bS, Pii as bT, PiiAddress as bU, PiiJurisdiction as bV, type PiiKind as bW, type QueryParamValue as bX, type QueryParamValues as bY, RedirectUri as bZ, type RemoveOptionAddressParams as b_, type CreateKycTokenParams as ba, CreateParticipationParams as bb, CreateWalletOwnershipChallengeParams as bc, Cursor as bd, type DocumentFormType as be, type DocumentSubmissionStatus as bf, type DocumentType as bg, ETHEREUM_CHAINS as bh, EpochMillis as bi, Erc20NamespaceImpl as bj, FaqItem as bk, type GetCentrifugeVaultQuoteParams as bl, type GetCentrifugeVaultStatusParams as bm, type GetOndoQuoteParams as bn, type GetOndoTradingStatusParams as bo, type GetSwapAuthorizationParams as bp, type GetSwapPreviewParams as bq, type GetTokenAllowanceParams as br, type GetTokenBalanceParams as bs, HexEncodedTransactionData as bt, Isin as bu, Iso2CountryCode as bv, Link as bw, type ListOptionAddressesParams as bx, MAX_ASSET_DECIMALS as by, MAX_UINT_256 as bz, type CentrifugeNamespace as c, type RpcConfig as c0, SOLANA_CHAINS as c1, STABLE_DECIMALS as c2, SolanaChain as c3, type SubmitDocumentParams as c4, type SuperstateSwapOffer as c5, type SwapContractRef as c6, SwapPreview as c7, TermItem as c8, Ticker as c9, type TokenLogoImage as ca, TokenLogoUrl as cb, type TokenRole as cc, TokensNamespaceImpl as cd, type Tx as ce, WalletAddress as cf, WalletOwnershipChallenge as cg, type WalletProtocol as ch, WalletsNamespaceImpl as ci, apiErrorCode as cj, assertUint256 as ck, isChain as cl, isExpired as cm, isOfferType as cn, parseUint256 as co, AssetSymbol as d, CentrifugeVaultBuyTransaction as e, type CentrifugeClaimDirection as f, CentrifugeNamespaceImpl as g, type CoinListTokenSaleNamespace as h, OfferId as i, OfferOptionId as j, AssetId as k, CoinListTokenSaleNamespaceImpl as l, type OndoNamespace as m, OfferSwapContract as n, OndoBuyTransaction as o, OndoSellTransaction as p, type OndoSwapTransactionCore as q, type OrderBookSide as r, OndoNamespaceImpl as s, type WalletsNamespace as t, Bps as u, EvmWalletAddress as v, SuperstateSwapNamespaceImpl as w, Requirement as x, RequirementsNamespaceImpl as y, type Config as z };
3987
+ export { Offer as $, AuthorizationCode as A, BlockchainAmount as B, CodeVerifier as C, type OAuthAccessToken as D, type Erc20Namespace as E, type OffersNamespace as F, StablecoinSymbol as G, type Erc20Asset as H, OfferDetail as I, TokenMetadata as J, type CentrifugeVaultOfferDetail as K, type Logger as L, type OfferType as M, type OndoSwapOfferDetail as N, OfferVaultContract as O, Participation as P, type CoinListTokenSaleOfferDetail as Q, type RequirementsNamespace as R, type SuperstateSwapNamespace as S, type TokensNamespace as T, type UniversalNamespaceContext as U, type SuperstateSwapOfferDetail as V, type WalletError as W, CentrifugeVaultLegId as X, Seconds as Y, type AsyncOfferType as Z, AsyncOfferDetail as _, EthereumChain as a, CentrifugeVaultPosition as a$, RequirementStatusInfo as a0, type RequirementType as a1, type RequirementStatusValue as a2, OfferOptionAddress as a3, RequirementId as a4, type KycLevelName as a5, DocumentSubmission as a6, type WalletChallengeType as a7, type PinoLoggerOptions as a8, AssetDecimals as a9, type RequestId as aA, type RpcFailureReason as aB, type SafeEvent as aC, type SafeFields as aD, type UnredactedFields as aE, OAuthSession as aF, ClientCredentialsOAuth as aG, type Sender as aH, PaginationParams as aI, PaginatedResponse as aJ, ClientSecret as aK, OfferToken as aL, DecimalString as aM, SwapStatus as aN, type Uint256 as aO, KnownAssetSymbol as aP, type Newtype as aQ, type AllowWalletParams as aR, AllowWalletResponse as aS, Asset as aT, AssetCode as aU, Blockchain as aV, type BuildOndoSwapParamsCore as aW, type CentrifugePositionStatus as aX, CentrifugeVaultClaimProceedsTransaction as aY, CentrifugeVaultClaimTransaction as aZ, type CentrifugeVaultOffer as a_, type BuildCentrifugeVaultBuyParams as aa, type CentrifugeOrderReason as ab, type CentrifugeOrderability as ac, OndoTradingStatus as ad, type BuildOndoBuyParams as ae, type BuildOndoSellParams as af, OndoQuote as ag, type OndoQuoteSize as ah, type TokenIdentifier as ai, CentrifugeVaultLeg as aj, OfferOptionAddressId as ak, CentrifugeVaultStatus as al, TokenLogo as am, type DebugEvent as an, type FrontlineEventId as ao, HttpError as ap, type HttpResponse as aq, KycToken as ar, type LogBinding as as, type LogBindings as at, type LogCause as au, type LogLevel as av, type LogScope as aw, type LogValue as ax, type ProductionLogLevel as ay, RedactedWalletError as az, EvmContractAddress as b, SOLANA_CHAINS as b$, type CentrifugeVaultReason as b0, CentrifugeVaultRedeemPosition as b1, type CentrifugeVaultTransactionCore as b2, type CentrifugeWalletParams as b3, Chain as b4, ClientId as b5, CodeChallenge as b6, type CoinListTokenSaleOffer as b7, ConnectExternalWalletParams as b8, type CreateKycTokenParams as b9, OAuthRefreshToken as bA, type OfferBase as bB, type OfferDetailBase as bC, OfferEditorial as bD, OfferOption as bE, OfferOptionSlug as bF, OfferSlug as bG, OffersNamespaceImpl as bH, type OndoQuoteDuration as bI, type OndoSellOutcome as bJ, type OndoSwapOffer as bK, PKCEState as bL, type PaginatedResponseDto as bM, ParticipationId as bN, type ParticipationStatus as bO, ParticipationsPaginationParams as bP, PauseFlags as bQ, Pii as bR, PiiAddress as bS, PiiJurisdiction as bT, type PiiKind as bU, type QueryParamValue as bV, type QueryParamValues as bW, RedirectUri as bX, type RemoveOptionAddressParams as bY, type RequirementActionNeededReason as bZ, type RpcConfig as b_, CreateParticipationParams as ba, CreateWalletOwnershipChallengeParams as bb, Cursor as bc, type DocumentFormType as bd, type DocumentSubmissionStatus as be, type DocumentType as bf, ETHEREUM_CHAINS as bg, EpochMillis as bh, Erc20NamespaceImpl as bi, FaqItem as bj, type GetCentrifugeVaultStatusParams as bk, type GetOndoQuoteParams as bl, type GetOndoTradingStatusParams as bm, type GetSwapAuthorizationParams as bn, type GetSwapPreviewParams as bo, type GetTokenAllowanceParams as bp, type GetTokenBalanceParams as bq, HexEncodedTransactionData as br, Isin as bs, Iso2CountryCode as bt, Link as bu, type ListOptionAddressesParams as bv, MAX_ASSET_DECIMALS as bw, MAX_UINT_256 as bx, Milestone as by, Millis as bz, type CentrifugeNamespace as c, STABLE_DECIMALS as c0, SolanaChain as c1, type SubmitDocumentParams as c2, type SuperstateSwapOffer as c3, type SwapContractRef as c4, SwapPreview as c5, TermItem as c6, Ticker as c7, type TokenLogoImage as c8, TokenLogoUrl as c9, type TokenRole as ca, TokensNamespaceImpl as cb, type Tx as cc, WalletAddress as cd, WalletOwnershipChallenge as ce, type WalletProtocol as cf, WalletsNamespaceImpl as cg, apiErrorCode as ch, assertUint256 as ci, isChain as cj, isExpired as ck, isOfferType as cl, parseUint256 as cm, AssetSymbol as d, CentrifugeVaultBuyTransaction as e, type CentrifugeClaimDirection as f, CentrifugeNamespaceImpl as g, type CoinListTokenSaleNamespace as h, OfferId as i, OfferOptionId as j, AssetId as k, CoinListTokenSaleNamespaceImpl as l, type OndoNamespace as m, OfferSwapContract as n, OndoBuyTransaction as o, OndoSellTransaction as p, type OndoSwapTransactionCore as q, type OrderBookSide as r, OndoNamespaceImpl as s, type WalletsNamespace as t, Bps as u, EvmWalletAddress as v, SuperstateSwapNamespaceImpl as w, Requirement as x, RequirementsNamespaceImpl as y, type Config as z };