@coinlist-co/react 0.10.1 → 0.11.1-rc.10770e8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +32 -0
  2. package/dist/chunk-7CTH4KPU.js +2399 -0
  3. package/dist/chunk-7CTH4KPU.js.map +1 -0
  4. package/dist/{chunk-AQVCOWOV.js → chunk-LSPZETDH.js} +249 -317
  5. package/dist/chunk-LSPZETDH.js.map +1 -0
  6. package/dist/chunk-UZUQALFY.js +279 -0
  7. package/dist/chunk-UZUQALFY.js.map +1 -0
  8. package/dist/client/index.cjs +13430 -3308
  9. package/dist/client/index.cjs.map +1 -1
  10. package/dist/client/index.d.cts +5486 -899
  11. package/dist/client/index.d.ts +5486 -899
  12. package/dist/client/index.js +11025 -2388
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/collections-BBI_XydI.d.cts +116 -0
  15. package/dist/collections-BrX9rRWc.d.ts +116 -0
  16. package/dist/config-CMl1bR3F.d.cts +2959 -0
  17. package/dist/config-CMl1bR3F.d.ts +2959 -0
  18. package/dist/server/index.cjs +1768 -511
  19. package/dist/server/index.cjs.map +1 -1
  20. package/dist/server/index.d.cts +266 -162
  21. package/dist/server/index.d.ts +266 -162
  22. package/dist/server/index.js +235 -169
  23. package/dist/server/index.js.map +1 -1
  24. package/dist/shared/index.cjs +2423 -926
  25. package/dist/shared/index.cjs.map +1 -1
  26. package/dist/shared/index.d.cts +325 -132
  27. package/dist/shared/index.d.ts +325 -132
  28. package/dist/shared/index.js +112 -28
  29. package/package.json +12 -8
  30. package/dist/chunk-AQVCOWOV.js.map +0 -1
  31. package/dist/chunk-TBU3EBNM.js +0 -442
  32. package/dist/chunk-TBU3EBNM.js.map +0 -1
  33. package/dist/chunk-UOHD7US2.js +0 -855
  34. package/dist/chunk-UOHD7US2.js.map +0 -1
  35. package/dist/collections-Bv1Oxzu_.d.ts +0 -28
  36. package/dist/collections-DDyxbOPZ.d.cts +0 -28
  37. package/dist/requirement-oVZA1INj.d.cts +0 -1040
  38. package/dist/requirement-oVZA1INj.d.ts +0 -1040
@@ -1,34 +1,40 @@
1
1
  import {
2
- API_VERSION,
3
2
  AuthenticatedApiClient,
3
+ NABU_BASE_URL,
4
+ PINO_LEVEL,
5
+ SDK_LOGGER_NAME,
6
+ loggerOverPino
7
+ } from "../chunk-UZUQALFY.js";
8
+ import {
9
+ API_VERSION,
10
+ ClientCredentialsOAuth,
11
+ CoinListTokenSaleNamespaceImpl,
12
+ Erc20NamespaceImpl,
4
13
  HttpError,
14
+ NotAuthenticatedError,
15
+ OAuthSession,
16
+ OndoNamespaceImpl,
5
17
  PUBLIC_API_BASE_URL,
6
- createKycToken,
18
+ RequirementsNamespaceImpl,
19
+ SuperstateSwapNamespaceImpl,
20
+ TokensNamespaceImpl,
21
+ WalletsNamespaceImpl,
7
22
  fetchOfferDetails,
8
23
  fetchOfferRequirements,
9
24
  fetchOffers,
10
25
  fetchOffersPage,
11
- fetchPii,
12
- fetchRequirementStatuses,
13
- submitDocument
14
- } from "../chunk-TBU3EBNM.js";
15
- import {
16
- ClientCredentialsOAuth,
17
- Erc20NamespaceImpl,
18
- NotAuthenticatedError,
19
- OAuthSession,
20
- SwapNamespaceImpl,
21
- TokenSaleNamespaceImpl,
22
- connectExternalWallet,
23
- createWalletOwnershipChallenge,
24
- listOptionAddresses,
25
- removeOptionAddress
26
- } from "../chunk-UOHD7US2.js";
26
+ internalLogger
27
+ } from "../chunk-7CTH4KPU.js";
27
28
 
28
- // src/server/api/api.server.ts
29
- var Api = class {
30
- constructor(config, fetchAccessToken) {
31
- this.client = new AuthenticatedApiClient(config, fetchAccessToken);
29
+ // src/server/core/api/api-client.ts
30
+ var ApiClient = class {
31
+ constructor(config, fetchAccessToken, logger = null) {
32
+ this.client = new AuthenticatedApiClient(
33
+ config,
34
+ fetchAccessToken,
35
+ [],
36
+ logger
37
+ );
32
38
  }
33
39
  async send(request) {
34
40
  return this.client.send(request);
@@ -43,87 +49,68 @@ var WritableSessionStoreRequiredError = class extends Error {
43
49
  }
44
50
  };
45
51
 
46
- // src/server/coinlist.server.ts
47
- var ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;
48
- var CoinListServerImpl = class {
49
- constructor(_config) {
50
- this._config = _config;
51
- this.baseUrl = _config.baseUrl ?? PUBLIC_API_BASE_URL;
52
- this.accessTokenExpiryBufferSeconds = _config.accessTokenExpiryBufferSeconds ?? ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS;
53
- this.strict = _config.strict ?? false;
54
- this.api = new Api(
55
- {
56
- baseUrl: this.baseUrl,
57
- xApiVersion: API_VERSION
58
- },
59
- // When refresh=true the renewal middleware has received a 401 and wants a
60
- // fresh token. A read-only store cannot persist a new session, so return
61
- // null immediately — this tells the middleware to skip the retry rather
62
- // than re-sending with the same expired token and wasting a round-trip.
63
- (refresh) => refresh && !this._config.sessionStore.setSession ? Promise.resolve(null) : this.accessToken()
64
- );
65
- const ctx = {
66
- api: this.api,
67
- ensureUserAuthenticated: () => this.ensureUserAuthenticated()
68
- };
69
- this.erc20 = new Erc20NamespaceImpl(ctx);
70
- this.tokenSale = new TokenSaleNamespaceImpl(ctx);
71
- this.swap = new SwapNamespaceImpl(ctx);
52
+ // src/server/core/server-auth-namespace.ts
53
+ function emptySessionStore() {
54
+ return { getSession: async () => null };
55
+ }
56
+ var ServerAuthNamespaceImpl = class {
57
+ constructor(api, config) {
58
+ this.api = api;
59
+ this.config = config;
60
+ this.log = internalLogger(config.logger ?? null, "AUTH");
72
61
  }
73
- async completeOAuth(code, codeVerifier) {
74
- const sessionStore = this._config.sessionStore;
75
- const setSession = sessionStore.setSession?.bind(sessionStore);
76
- if (!setSession) {
77
- throw new WritableSessionStoreRequiredError();
78
- }
79
- const sessionDto = await this.api.send({
80
- method: "POST",
81
- url: `/oauth/token`,
82
- body: {
83
- grant_type: "authorization_code",
84
- code,
85
- redirect_uri: this._config.redirectUri,
86
- client_id: this._config.clientId,
87
- client_secret: this._config.clientSecret,
88
- code_verifier: codeVerifier
89
- }
90
- });
91
- const session = OAuthSession.fromDto(sessionDto);
92
- await setSession(session);
93
- return session;
94
- }
95
- async clientCredentialsOAuth() {
96
- const sessionDto = await this.api.send({
97
- method: "POST",
98
- url: `/oauth/token`,
99
- body: {
100
- grant_type: "client_credentials",
101
- client_id: this._config.clientId,
102
- client_secret: this._config.clientSecret
103
- }
62
+ async completeOAuth(params) {
63
+ return this.log.wrap("completeOAuth", void 0, async () => {
64
+ const setSession = this.writableSessionStore();
65
+ const sessionDto = await this.api.send({
66
+ method: "POST",
67
+ url: `/oauth/token`,
68
+ body: {
69
+ grant_type: "authorization_code",
70
+ code: params.code,
71
+ redirect_uri: this.config.redirectUri,
72
+ client_id: this.config.clientId,
73
+ client_secret: this.config.clientSecret,
74
+ code_verifier: params.codeVerifier
75
+ }
76
+ });
77
+ const session = OAuthSession.fromDto(sessionDto);
78
+ await setSession(session);
79
+ return session;
104
80
  });
105
- const session = OAuthSession.fromDto(sessionDto);
106
- return ClientCredentialsOAuth(session.accessToken);
107
81
  }
108
- async accessToken() {
109
- const sessionStore = this._config.sessionStore;
82
+ async getAccessToken() {
83
+ return this.log.wrap(
84
+ "getAccessToken",
85
+ void 0,
86
+ () => this.readAccessToken()
87
+ );
88
+ }
89
+ async readAccessToken() {
90
+ const sessionStore = this.config.sessionStore;
110
91
  const session = await sessionStore.getSession();
111
92
  if (session == null) return null;
112
93
  const now = Date.now();
113
94
  const expiresAt = session.accessToken.expiresAt.getTime();
114
- const bufferMs = this.accessTokenExpiryBufferSeconds * 1e3;
95
+ const bufferMs = this.config.accessTokenExpiryBufferSeconds * 1e3;
115
96
  if (expiresAt > now + bufferMs) {
116
97
  return session.accessToken;
117
98
  }
118
99
  const setSession = sessionStore.setSession?.bind(sessionStore);
119
100
  if (!setSession) {
101
+ this.log.child({ op: "getAccessToken" }).warn(() => ({
102
+ msg: "serving an expired token: the session store is read-only, so it cannot be refreshed"
103
+ }));
120
104
  return session.accessToken;
121
- } else {
122
- return this.refreshSession(session.refreshToken, setSession);
123
105
  }
106
+ return this.refreshSession(session.refreshToken, setSession);
124
107
  }
125
108
  async refreshSession(refreshToken, setSession) {
109
+ const log = this.log.child({ op: "refresh" });
126
110
  if (!refreshToken) {
111
+ log.warn(() => ({
112
+ msg: "clearing the session: it carries no refresh token"
113
+ }));
127
114
  await setSession(null);
128
115
  return null;
129
116
  }
@@ -134,122 +121,201 @@ var CoinListServerImpl = class {
134
121
  body: {
135
122
  grant_type: "refresh_token",
136
123
  refresh_token: refreshToken,
137
- client_id: this._config.clientId,
138
- client_secret: this._config.clientSecret
124
+ client_id: this.config.clientId,
125
+ client_secret: this.config.clientSecret
139
126
  }
140
127
  });
141
128
  const newSession = OAuthSession.fromDto(sessionDto);
142
129
  await setSession(newSession);
130
+ log.info(() => ({ msg: "session renewed" }));
143
131
  return newSession.accessToken;
144
- } catch {
132
+ } catch (error) {
133
+ log.failure(
134
+ () => ({ msg: "refresh failed; clearing the session" }),
135
+ error
136
+ );
145
137
  await setSession(null);
146
138
  return null;
147
139
  }
148
140
  }
141
+ async clientCredentials() {
142
+ return this.log.wrap("clientCredentials", void 0, async () => {
143
+ const sessionDto = await this.api.send({
144
+ method: "POST",
145
+ url: `/oauth/token`,
146
+ body: {
147
+ grant_type: "client_credentials",
148
+ client_id: this.config.clientId,
149
+ client_secret: this.config.clientSecret
150
+ }
151
+ });
152
+ const session = OAuthSession.fromDto(sessionDto);
153
+ return ClientCredentialsOAuth(session.accessToken);
154
+ });
155
+ }
149
156
  async logout() {
150
- const sessionStore = this._config.sessionStore;
151
- const setSession = sessionStore.setSession?.bind(sessionStore);
152
- if (!setSession) {
153
- throw new WritableSessionStoreRequiredError();
154
- }
155
- const session = await sessionStore.getSession();
156
- if (session != null) {
157
- const tokenToRevoke = session.accessToken.value;
158
- try {
159
- await this.api.send({
160
- method: "POST",
161
- url: `/oauth/revoke`,
162
- body: {
163
- token: tokenToRevoke,
164
- client_id: this._config.clientId,
165
- client_secret: this._config.clientSecret
166
- }
167
- });
168
- } catch (err) {
169
- if (err instanceof HttpError) {
170
- if (this.strict) {
171
- throw err;
172
- }
173
- } else {
174
- throw err;
157
+ return this.log.wrap("logout", void 0, () => this.revokeAndClear());
158
+ }
159
+ async revokeAndClear() {
160
+ const setSession = this.writableSessionStore();
161
+ const session = await this.config.sessionStore.getSession();
162
+ if (session == null) return;
163
+ try {
164
+ await this.api.send({
165
+ method: "POST",
166
+ url: `/oauth/revoke`,
167
+ body: {
168
+ token: session.accessToken.value,
169
+ client_id: this.config.clientId,
170
+ client_secret: this.config.clientSecret
175
171
  }
172
+ });
173
+ } catch (err) {
174
+ if (!(err instanceof HttpError) || this.config.strict) {
175
+ throw err;
176
176
  }
177
- await setSession(null);
177
+ this.log.child({ op: "logout" }).warn(() => ({
178
+ msg: "the token was not revoked upstream; the local session is cleared regardless"
179
+ }));
178
180
  }
181
+ await setSession(null);
179
182
  }
180
- async fetchOffers(clientCreds) {
181
- await this.ensureAuthenticated(clientCreds);
182
- return fetchOffers(this.api, clientCreds);
183
+ /**
184
+ * Returns the store's `setSession`, or throws — the guard every
185
+ * session-writing operation shares.
186
+ */
187
+ writableSessionStore() {
188
+ const sessionStore = this.config.sessionStore;
189
+ const setSession = sessionStore.setSession?.bind(sessionStore);
190
+ if (!setSession) {
191
+ throw new WritableSessionStoreRequiredError();
192
+ }
193
+ return setSession;
183
194
  }
184
- async fetchOffersPage(params, clientCreds) {
185
- await this.ensureAuthenticated(clientCreds);
186
- return fetchOffersPage(this.api, params, clientCreds);
195
+ };
196
+
197
+ // src/server/core/server-offers-namespace.ts
198
+ var ServerOffersNamespaceImpl = class {
199
+ constructor(ctx) {
200
+ this.ctx = ctx;
201
+ this.log = internalLogger(ctx.logger, "OFFERS");
187
202
  }
188
- async fetchOfferDetails(id, clientCreds) {
189
- await this.ensureAuthenticated(clientCreds);
190
- return fetchOfferDetails(this.api, id, clientCreds);
203
+ async list(clientCreds) {
204
+ return this.log.wrap("list", clientCreds, async () => {
205
+ await this.ctx.ensureAuthenticated(clientCreds);
206
+ return fetchOffers(this.ctx.api, clientCreds);
207
+ });
191
208
  }
192
- async createWalletOwnershipChallenge(params) {
193
- await this.ensureUserAuthenticated();
194
- return createWalletOwnershipChallenge(this.api, params);
209
+ async listPage(params, clientCreds) {
210
+ return this.log.wrap("listPage", { params, clientCreds }, async () => {
211
+ await this.ctx.ensureAuthenticated(clientCreds);
212
+ return fetchOffersPage(this.ctx.api, params, clientCreds);
213
+ });
195
214
  }
196
- async connectExternalWallet(offerId, params) {
197
- await this.ensureUserAuthenticated();
198
- return connectExternalWallet(this.api, offerId, params);
215
+ async get(id, clientCreds) {
216
+ return this.log.wrap("get", { id, clientCreds }, async () => {
217
+ await this.ctx.ensureAuthenticated(clientCreds);
218
+ return fetchOfferDetails(this.ctx.api, id, clientCreds);
219
+ });
199
220
  }
200
- async listOptionAddresses(offerId, offerOptionId) {
201
- await this.ensureUserAuthenticated();
202
- return listOptionAddresses(
203
- this.api,
204
- offerId,
205
- offerOptionId
206
- );
221
+ };
222
+
223
+ // src/server/core/server-requirements-namespace.ts
224
+ var ServerRequirementsNamespaceImpl = class extends RequirementsNamespaceImpl {
225
+ constructor(serverCtx) {
226
+ super(serverCtx);
227
+ this.serverCtx = serverCtx;
207
228
  }
208
- async removeOptionAddress(offerId, addressId) {
209
- await this.ensureUserAuthenticated();
210
- return removeOptionAddress(this.api, offerId, addressId);
229
+ async forOffer(offerId, clientCreds) {
230
+ return this.log.wrap("forOffer", { offerId, clientCreds }, async () => {
231
+ await this.serverCtx.ensureAuthenticated(clientCreds);
232
+ return fetchOfferRequirements(
233
+ this.serverCtx.api,
234
+ offerId,
235
+ clientCreds
236
+ );
237
+ });
211
238
  }
212
- async fetchOfferRequirements(offerId, clientCreds) {
213
- await this.ensureAuthenticated(clientCreds);
214
- return fetchOfferRequirements(
215
- this.api,
216
- offerId,
217
- clientCreds
239
+ };
240
+
241
+ // src/server/core/coinlist-server.ts
242
+ var ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;
243
+ var CoinListServerImpl = class {
244
+ constructor(_config) {
245
+ this._config = _config;
246
+ const logger = _config.logger ?? null;
247
+ this.api = new ApiClient(
248
+ {
249
+ baseUrl: _config.baseUrl ?? PUBLIC_API_BASE_URL,
250
+ xApiVersion: API_VERSION
251
+ },
252
+ // When refresh=true the renewal middleware has received a 401 and wants a
253
+ // fresh token. A read-only store cannot persist a new session, so return
254
+ // null immediately — this tells the middleware to skip the retry rather
255
+ // than re-sending with the same expired token and wasting a round-trip.
256
+ (refresh) => refresh && !this._config.sessionStore.setSession ? Promise.resolve(null) : this.auth.getAccessToken(),
257
+ logger
258
+ );
259
+ this.auth = new ServerAuthNamespaceImpl(this.api, {
260
+ ..._config,
261
+ accessTokenExpiryBufferSeconds: _config.accessTokenExpiryBufferSeconds ?? ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS,
262
+ strict: _config.strict ?? false
263
+ });
264
+ const ctx = {
265
+ api: this.api,
266
+ logger,
267
+ ensureUserAuthenticated: () => this.ensureAuthenticated(void 0),
268
+ ensureAuthenticated: (clientCreds) => this.ensureAuthenticated(clientCreds)
269
+ };
270
+ this.offers = new ServerOffersNamespaceImpl(ctx);
271
+ this.requirements = new ServerRequirementsNamespaceImpl(ctx);
272
+ this.wallets = new WalletsNamespaceImpl(ctx);
273
+ this.erc20 = new Erc20NamespaceImpl(ctx);
274
+ this.tokenSale = new CoinListTokenSaleNamespaceImpl(ctx);
275
+ this.superstate = new SuperstateSwapNamespaceImpl(ctx);
276
+ this.ondo = new OndoNamespaceImpl(ctx);
277
+ this.tokens = new TokensNamespaceImpl(
278
+ _config.tokensBaseUrl ?? NABU_BASE_URL,
279
+ logger
218
280
  );
219
281
  }
220
- async fetchRequirementStatuses(offerId) {
221
- await this.ensureUserAuthenticated();
222
- return fetchRequirementStatuses(this.api, offerId);
223
- }
282
+ /**
283
+ * An app-level token authenticates a request on its own; without one the
284
+ * caller needs a user session.
285
+ */
224
286
  async ensureAuthenticated(clientCreds) {
225
- if (!clientCreds) {
226
- await this.ensureUserAuthenticated();
227
- }
228
- }
229
- async ensureUserAuthenticated() {
230
- const token = await this.accessToken();
287
+ if (clientCreds) return;
288
+ const token = await this.auth.getAccessToken();
231
289
  if (token === null) {
232
290
  throw new NotAuthenticatedError();
233
291
  }
234
292
  }
235
- async fetchPii() {
236
- await this.ensureUserAuthenticated();
237
- return fetchPii(this.api);
238
- }
239
- async submitDocument(documentType, fields) {
240
- await this.ensureUserAuthenticated();
241
- return submitDocument(this.api, documentType, fields);
242
- }
243
- async createKycToken(levelName, reset) {
244
- await this.ensureUserAuthenticated();
245
- return createKycToken(this.api, levelName, reset);
246
- }
247
293
  };
248
294
  function createCoinListServer(config) {
249
295
  return new CoinListServerImpl(config);
250
296
  }
297
+
298
+ // src/server/core/observability/pino-server-logger.ts
299
+ import { pino } from "pino";
300
+ function pinoServerLogger(options) {
301
+ return loggerOverPino(
302
+ // `name` as a child binding rather than pino's `name` option: the option
303
+ // is honoured by pino's node build and silently dropped by its browser
304
+ // build, so a binding is the only spelling that identifies the SDK in
305
+ // both environments.
306
+ pino({
307
+ level: PINO_LEVEL[options.level]
308
+ }).child({ name: SDK_LOGGER_NAME }),
309
+ options.level
310
+ );
311
+ }
251
312
  export {
313
+ ServerAuthNamespaceImpl,
314
+ ServerOffersNamespaceImpl,
315
+ ServerRequirementsNamespaceImpl,
252
316
  WritableSessionStoreRequiredError,
253
- createCoinListServer
317
+ createCoinListServer,
318
+ emptySessionStore,
319
+ pinoServerLogger
254
320
  };
255
321
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/api/api.server.ts","../../src/server/errors.ts","../../src/server/coinlist.server.ts"],"sourcesContent":["import { AuthenticatedApiClient } from '@/shared/api/authenticated-api-client';\nimport type { HttpClientConfig, HttpRequest } from '@/shared/api/http';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport class Api {\n private readonly client: AuthenticatedApiClient;\n\n constructor(\n config: HttpClientConfig,\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>\n ) {\n this.client = new AuthenticatedApiClient(config, fetchAccessToken);\n }\n\n async send<TResponse>(request: HttpRequest): Promise<TResponse> {\n return this.client.send<TResponse>(request);\n }\n}\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 { Api } from '@/server/api/api.server';\nimport { WritableSessionStoreRequiredError } from '@/server/errors';\nimport {\n API_VERSION,\n PUBLIC_API_BASE_URL,\n} from '@/shared/api/frontline/config';\nimport * as documentsApi from '@/shared/api/frontline/documents';\nimport * as kycApi from '@/shared/api/frontline/kyc';\nimport * as offersApi from '@/shared/api/frontline/offers';\nimport * as piiApi from '@/shared/api/frontline/pii';\nimport * as requirementsApi from '@/shared/api/frontline/requirements';\nimport * as walletConnectApi from '@/shared/api/frontline/wallet-connect';\nimport { HttpError } from '@/shared/api/http';\nimport type {\n PaginatedResponse,\n PaginationParams,\n} from '@/shared/api/pagination';\nimport {\n type CoinListErc20Namespace,\n Erc20NamespaceImpl,\n} from '@/shared/core/erc20-namespace';\nimport {\n type CoinListSwapNamespace,\n SwapNamespaceImpl,\n} from '@/shared/core/swap-namespace';\nimport {\n type CoinListTokenSaleNamespace,\n TokenSaleNamespaceImpl,\n} from '@/shared/core/token-sale-namespace';\nimport type { Config } from '@/shared/types/config';\nimport type {\n DocumentSubmission,\n DocumentType,\n} from '@/shared/types/document-submission';\nimport type { OAuthSessionDto } from '@/shared/types/dto/oauth-session';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport type { KycLevelName, KycToken } from '@/shared/types/kyc';\nimport type {\n AuthorizationCode,\n ClientSecret,\n CodeVerifier,\n} from '@/shared/types/oauth';\nimport {\n ClientCredentialsOAuth,\n type OAuthAccessToken,\n type OAuthRefreshToken,\n OAuthSession,\n} from '@/shared/types/oauth-session';\nimport type { Offer, OfferId } from '@/shared/types/offer';\nimport type { OfferDetail, OfferOptionId } from '@/shared/types/offer-detail';\nimport type {\n ConnectExternalWalletParams,\n OfferOptionAddress,\n OfferOptionAddressId,\n} from '@/shared/types/offer-option-address';\nimport type { Pii } from '@/shared/types/pii';\nimport type {\n Requirement,\n RequirementStatusInfo,\n} from '@/shared/types/requirement';\nimport type {\n CreateWalletOwnershipChallengeParams,\n WalletOwnershipChallenge,\n} from '@/shared/types/wallet-ownership-challenge';\n\n/** Buffer in seconds before expiry to consider token expired for refresh. */\nconst ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;\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 CoinListServer.completeOAuth} and {@link CoinListServer.logout}\n * always require a writable store and throw\n * {@link WritableSessionStoreRequiredError} if `setSession` is absent.\n */\n setSession?(session: OAuthSession | null): Promise<void>;\n}\n\nexport interface ServerConfig extends Config {\n readonly clientSecret: ClientSecret;\n readonly sessionStore: SessionStore;\n /** Buffer in seconds before expiry to consider token expired for refresh. */\n readonly accessTokenExpiryBufferSeconds?: number;\n /**\n * Whether SDK will throw an exception in cases it can be silent.\n * For example, if token revokation on logout fails.\n */\n readonly strict?: boolean;\n}\n\n/**\n * Server-side CoinList SDK client.\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, {@link completeOAuth}, and {@link 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 * {@link completeOAuth} and {@link logout} throw\n * {@link WritableSessionStoreRequiredError}. {@link accessToken} may return\n * an expired token (see its docs). Use this mode in execution contexts that\n * can read the session but cannot write it back, such as Next.js Server\n * Components.\n */\nexport interface CoinListServer {\n /**\n * Exchanges an authorization code for an OAuth session and persists it via\n * {@link SessionStore.setSession}.\n *\n * Throws {@link WritableSessionStoreRequiredError} if the session store does\n * not provide `setSession`.\n */\n completeOAuth(\n code: AuthorizationCode,\n codeVerifier: CodeVerifier\n ): Promise<OAuthSession>;\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 {@link fetchOffers}, {@link fetchOffersPage},\n * {@link fetchOfferDetails}, or {@link fetchOfferRequirements} to call them\n * without a user session.\n */\n clientCredentialsOAuth(): Promise<ClientCredentialsOAuth>;\n\n /**\n * Returns a valid access token for the current session, refreshing it if\n * it is expired or near expiry.\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 * Returns `null` if there is no session or the refresh fails.\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 session\n * could not be refreshed.\n */\n accessToken(): Promise<OAuthAccessToken | null>;\n\n /**\n * Revokes the current token via POST /oauth/revoke and clears the session.\n *\n * Throws {@link WritableSessionStoreRequiredError} if the session store does\n * not provide `setSession`.\n */\n logout(): Promise<void>;\n\n /**\n * Fetches all offers by iterating through every paginated response.\n *\n * @param clientCreds optional app-level token from\n * {@link clientCredentialsOAuth}. When provided, no user session is\n * required; if a user session exists it takes precedence and `clientCreds`\n * is used as a fallback.\n *\n * Throws {@link NotAuthenticatedError} if there is neither a user session\n * nor `clientCreds`.\n */\n fetchOffers(clientCreds?: ClientCredentialsOAuth): Promise<Offer[]>;\n\n /**\n * Fetches a single page of offers.\n *\n * @param clientCreds optional app-level token from\n * {@link clientCredentialsOAuth}. When provided, no user session is\n * required; if a user session exists it takes precedence and `clientCreds`\n * is used as a fallback.\n *\n * Throws {@link NotAuthenticatedError} if there is neither a user session\n * nor `clientCreds`.\n */\n fetchOffersPage(\n params: PaginationParams,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<PaginatedResponse<Offer>>;\n\n /**\n * Fetches details for a given offer by its id.\n *\n * @param clientCreds optional app-level token from\n * {@link clientCredentialsOAuth}. When provided, no user session is\n * required; if a user session exists it takes precedence and `clientCreds`\n * is used as a fallback.\n *\n * Throws {@link NotAuthenticatedError} if there is neither a user session\n * nor `clientCreds`.\n */\n fetchOfferDetails(\n id: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<OfferDetail>;\n\n /**\n * Creates a single-use wallet-ownership challenge for the given wallet and\n * chain. The user signs the returned {@link WalletOwnershipChallenge.message}\n * with their wallet, then passes the signature to\n * {@link connectExternalWallet}.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n createWalletOwnershipChallenge(\n params: CreateWalletOwnershipChallengeParams\n ): Promise<WalletOwnershipChallenge>;\n\n /**\n * Connects a proven external wallet to an offer option, using a signature of\n * a challenge from {@link createWalletOwnershipChallenge}.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n connectExternalWallet(\n offerId: OfferId,\n params: ConnectExternalWalletParams\n ): Promise<OfferOptionAddress>;\n\n /**\n * Lists the user's proven wallet bindings for a single offer option.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n listOptionAddresses(\n offerId: OfferId,\n offerOptionId: OfferOptionId\n ): Promise<OfferOptionAddress[]>;\n\n /**\n * Removes one of the user's wallet bindings and returns the removed binding.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n removeOptionAddress(\n offerId: OfferId,\n addressId: OfferOptionAddressId\n ): Promise<OfferOptionAddress>;\n\n /**\n * Fetches the requirements for all options of a given offer, grouped by option ID.\n *\n * @param clientCreds optional app-level token from\n * {@link clientCredentialsOAuth}. When provided, no user session is\n * required; if a user session exists it takes precedence and `clientCreds`\n * is used as a fallback.\n *\n * Throws {@link NotAuthenticatedError} if there is neither a user session\n * nor `clientCreds`.\n */\n fetchOfferRequirements(\n offerId: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<Record<OfferOptionId, Requirement[]>>;\n\n /**\n * Fetches the user's requirement statuses for a given offer.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchRequirementStatuses(offerId: OfferId): Promise<RequirementStatusInfo[]>;\n\n /**\n * Fetches the user's PII (tax form pre-fill data) — full legal name, date\n * of birth, jurisdiction, tax ID, and permanent address for an individual;\n * or the equivalent entity fields for a company/trust, used to pre-fill a\n * W-8BEN. Fields the entity hasn't provided are `null`.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchPii(): Promise<Pii>;\n\n /**\n * Starts (or resumes) a document signing submission for the given type\n * (currently only `tax_certification`, e.g. W-8BEN/W-8BEN-E). `fields` are\n * signing-form values keyed by the document's DocuSeal field names,\n * forwarded verbatim to Passport to pre-fill the document.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n submitDocument(\n documentType: DocumentType,\n fields: Record<string, string>\n ): Promise<DocumentSubmission>;\n\n /**\n * Generic ERC-20 reads (token allowance and balance) — e.g.\n * `coinlist.erc20.getTokenBalance({ ... })`.\n *\n * These methods require a user session and throw\n * {@link NotAuthenticatedError} if the user is not authenticated.\n */\n readonly erc20: CoinListErc20Namespace;\n\n /**\n * Token-sale operations: listing, reading, and recording participations —\n * e.g. `coinlist.tokenSale.fetchParticipations()`. The on-chain\n * `executeTokenSale` flow is client-only and is not exposed here.\n *\n * These methods require a user session and throw\n * {@link NotAuthenticatedError} if the user is not authenticated.\n */\n readonly tokenSale: CoinListTokenSaleNamespace;\n\n /**\n * On-chain swap operations: quoting a swap, reading swap-contract state, and\n * proving/allow-listing wallet ownership — e.g.\n * `coinlist.swap.getOutputToken({ contractAddress, chain })`.\n *\n * These methods require a user session and throw\n * {@link NotAuthenticatedError} if the user is not authenticated.\n */\n readonly swap: CoinListSwapNamespace;\n\n /**\n * Creates a short-lived Sumsub WebSDK access token for the current user so\n * an identity verification (KYC) flow can be started, e.g. to seed the\n * client-side `IdentityVerification` component when server-rendering.\n * `levelName` selects the Sumsub verification level; defaults to the\n * backend's standard level. `reset` resets the Sumsub applicant first, so\n * an already-approved level can be executed again (e.g. to update stale\n * PII).\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n createKycToken(levelName?: KycLevelName, reset?: boolean): Promise<KycToken>;\n}\n\nclass CoinListServerImpl implements CoinListServer {\n private readonly api: Api;\n private readonly baseUrl: string;\n\n private readonly accessTokenExpiryBufferSeconds: number;\n private readonly strict: boolean;\n readonly erc20: CoinListErc20Namespace;\n readonly tokenSale: CoinListTokenSaleNamespace;\n readonly swap: CoinListSwapNamespace;\n\n constructor(private readonly _config: ServerConfig) {\n this.baseUrl = _config.baseUrl ?? PUBLIC_API_BASE_URL;\n this.accessTokenExpiryBufferSeconds =\n _config.accessTokenExpiryBufferSeconds ??\n ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS;\n this.strict = _config.strict ?? false;\n this.api = new Api(\n {\n baseUrl: this.baseUrl,\n xApiVersion: API_VERSION,\n },\n // When refresh=true the renewal middleware has received a 401 and wants a\n // fresh token. A read-only store cannot persist a new session, so return\n // null immediately — this tells the middleware to skip the retry rather\n // than re-sending with the same expired token and wasting a round-trip.\n (refresh) =>\n refresh && !this._config.sessionStore.setSession\n ? Promise.resolve(null)\n : this.accessToken()\n );\n const ctx = {\n api: this.api,\n ensureUserAuthenticated: () => this.ensureUserAuthenticated(),\n };\n this.erc20 = new Erc20NamespaceImpl(ctx);\n this.tokenSale = new TokenSaleNamespaceImpl(ctx);\n this.swap = new SwapNamespaceImpl(ctx);\n }\n\n async completeOAuth(\n code: AuthorizationCode,\n codeVerifier: CodeVerifier\n ): Promise<OAuthSession> {\n const sessionStore = this._config.sessionStore;\n const setSession = sessionStore.setSession?.bind(sessionStore);\n if (!setSession) {\n throw new WritableSessionStoreRequiredError();\n }\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'authorization_code',\n code,\n redirect_uri: this._config.redirectUri,\n client_id: this._config.clientId,\n client_secret: this._config.clientSecret,\n code_verifier: codeVerifier,\n },\n });\n const session = OAuthSession.fromDto(sessionDto);\n await setSession(session);\n return session;\n }\n\n async clientCredentialsOAuth(): Promise<ClientCredentialsOAuth> {\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 async accessToken(): Promise<OAuthAccessToken | null> {\n const sessionStore = this._config.sessionStore;\n const session = await sessionStore.getSession();\n if (session == null) return null;\n\n const now = Date.now();\n const expiresAt = session.accessToken.expiresAt.getTime();\n const bufferMs = this.accessTokenExpiryBufferSeconds * 1000;\n if (expiresAt > now + bufferMs) {\n // Valid access token, return it regardless\n return session.accessToken;\n }\n\n const setSession = sessionStore.setSession?.bind(sessionStore);\n if (!setSession) {\n // No write session capabilities => can't refresh!\n // Return the access token as-is\n return session.accessToken;\n } else {\n return this.refreshSession(session.refreshToken, setSession);\n }\n }\n\n private async refreshSession(\n refreshToken: OAuthRefreshToken | undefined,\n setSession: (session: OAuthSession | null) => Promise<void>\n ): Promise<OAuthAccessToken | null> {\n if (!refreshToken) {\n await setSession(null);\n return null;\n }\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 });\n const newSession = OAuthSession.fromDto(sessionDto);\n await setSession(newSession);\n return newSession.accessToken;\n } catch {\n await setSession(null);\n return null;\n }\n }\n\n async logout(): Promise<void> {\n const sessionStore = this._config.sessionStore;\n const setSession = sessionStore.setSession?.bind(sessionStore);\n if (!setSession) {\n throw new WritableSessionStoreRequiredError();\n }\n const session = await sessionStore.getSession();\n if (session != null) {\n const tokenToRevoke = session.accessToken.value;\n try {\n await this.api.send({\n method: 'POST',\n url: `/oauth/revoke`,\n body: {\n token: tokenToRevoke,\n client_id: this._config.clientId,\n client_secret: this._config.clientSecret,\n },\n });\n } catch (err) {\n if (err instanceof HttpError) {\n if (this.strict) {\n throw err;\n }\n } else {\n throw err;\n }\n }\n // invalidate the session\n await setSession(null);\n }\n }\n\n async fetchOffers(clientCreds?: ClientCredentialsOAuth): Promise<Offer[]> {\n await this.ensureAuthenticated(clientCreds);\n return offersApi.fetchOffers(this.api, clientCreds);\n }\n\n async fetchOffersPage(\n params: PaginationParams,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<PaginatedResponse<Offer>> {\n await this.ensureAuthenticated(clientCreds);\n return offersApi.fetchOffersPage(this.api, params, clientCreds);\n }\n\n async fetchOfferDetails(\n id: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<OfferDetail> {\n await this.ensureAuthenticated(clientCreds);\n return offersApi.fetchOfferDetails(this.api, id, clientCreds);\n }\n\n async createWalletOwnershipChallenge(\n params: CreateWalletOwnershipChallengeParams\n ): Promise<WalletOwnershipChallenge> {\n await this.ensureUserAuthenticated();\n return walletConnectApi.createWalletOwnershipChallenge(this.api, params);\n }\n\n async connectExternalWallet(\n offerId: OfferId,\n params: ConnectExternalWalletParams\n ): Promise<OfferOptionAddress> {\n await this.ensureUserAuthenticated();\n return walletConnectApi.connectExternalWallet(this.api, offerId, params);\n }\n\n async listOptionAddresses(\n offerId: OfferId,\n offerOptionId: OfferOptionId\n ): Promise<OfferOptionAddress[]> {\n await this.ensureUserAuthenticated();\n return walletConnectApi.listOptionAddresses(\n this.api,\n offerId,\n offerOptionId\n );\n }\n\n async removeOptionAddress(\n offerId: OfferId,\n addressId: OfferOptionAddressId\n ): Promise<OfferOptionAddress> {\n await this.ensureUserAuthenticated();\n return walletConnectApi.removeOptionAddress(this.api, offerId, addressId);\n }\n\n async fetchOfferRequirements(\n offerId: OfferId,\n clientCreds?: ClientCredentialsOAuth\n ): Promise<Record<OfferOptionId, Requirement[]>> {\n await this.ensureAuthenticated(clientCreds);\n return requirementsApi.fetchOfferRequirements(\n this.api,\n offerId,\n clientCreds\n );\n }\n\n async fetchRequirementStatuses(\n offerId: OfferId\n ): Promise<RequirementStatusInfo[]> {\n await this.ensureUserAuthenticated();\n return requirementsApi.fetchRequirementStatuses(this.api, offerId);\n }\n\n private async ensureAuthenticated(\n clientCreds: ClientCredentialsOAuth | undefined\n ): Promise<void> {\n if (!clientCreds) {\n await this.ensureUserAuthenticated();\n }\n }\n\n private async ensureUserAuthenticated(): Promise<void> {\n const token = await this.accessToken();\n if (token === null) {\n throw new NotAuthenticatedError();\n }\n }\n\n async fetchPii(): Promise<Pii> {\n await this.ensureUserAuthenticated();\n return piiApi.fetchPii(this.api);\n }\n\n async submitDocument(\n documentType: DocumentType,\n fields: Record<string, string>\n ): Promise<DocumentSubmission> {\n await this.ensureUserAuthenticated();\n return documentsApi.submitDocument(this.api, documentType, fields);\n }\n\n async createKycToken(\n levelName?: KycLevelName,\n reset?: boolean\n ): Promise<KycToken> {\n await this.ensureUserAuthenticated();\n return kycApi.createKycToken(this.api, levelName, reset);\n }\n}\n\nexport function createCoinListServer(config: ServerConfig): CoinListServer {\n return new CoinListServerImpl(config);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/server/core/api/api-client.ts","../../src/server/errors.ts","../../src/server/core/server-auth-namespace.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"],"sourcesContent":["import { AuthenticatedApiClient } from '@/shared/api/authenticated-api-client';\nimport type { HttpClientConfig, HttpRequest } from '@/shared/api/http';\nimport type { Logger } from '@/shared/types/logger';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport class ApiClient {\n private readonly client: AuthenticatedApiClient;\n\n constructor(\n config: HttpClientConfig,\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>,\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","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 { WritableSessionStoreRequiredError } from '@/server/errors';\nimport { HttpError } from '@/shared/api/http';\nimport type { Sender } from '@/shared/api/http-client';\nimport {\n type InternalLogger,\n internalLogger,\n} from '@/shared/core/observability/internal-logger';\nimport type { Config } from '@/shared/types/config';\nimport type { OAuthSessionDto } from '@/shared/types/dto/oauth-session';\nimport type {\n AuthorizationCode,\n ClientSecret,\n CodeVerifier,\n} from '@/shared/types/oauth';\nimport {\n ClientCredentialsOAuth,\n type OAuthAccessToken,\n type OAuthRefreshToken,\n OAuthSession,\n} from '@/shared/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 * Throws {@link WritableSessionStoreRequiredError} if the session store does\n * not provide `setSession`.\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}.\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 * Returns `null` if there is no session or the refresh fails.\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 session\n * could not be refreshed.\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 *\n * Throws {@link WritableSessionStoreRequiredError} if the session store does\n * not provide `setSession`.\n */\n logout(): Promise<void>;\n}\n\n/** What {@link ServerAuthNamespaceImpl} needs out of `ServerConfig`. */\nexport type ServerAuthConfig = Config & {\n readonly clientSecret: ClientSecret;\n readonly sessionStore: SessionStore;\n /** Seconds before expiry at which a token counts as expired for refresh. */\n readonly accessTokenExpiryBufferSeconds: number;\n /** Whether to rethrow errors the SDK could otherwise swallow (e.g. revoke). */\n readonly strict: boolean;\n};\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 });\n const session = OAuthSession.fromDto(sessionDto);\n await setSession(session);\n return session;\n });\n }\n\n async getAccessToken(): 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()\n );\n }\n\n private async readAccessToken(): Promise<OAuthAccessToken | null> {\n const sessionStore = this.config.sessionStore;\n const session = await sessionStore.getSession();\n if (session == null) return null;\n\n const now = Date.now();\n const expiresAt = session.accessToken.expiresAt.getTime();\n const bufferMs = this.config.accessTokenExpiryBufferSeconds * 1000;\n if (expiresAt > now + bufferMs) {\n // Valid access token, return it regardless\n return session.accessToken;\n }\n\n const setSession = sessionStore.setSession?.bind(sessionStore);\n if (!setSession) {\n // No write session capabilities => can't refresh!\n // Return the access token as-is\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 session.accessToken;\n }\n return this.refreshSession(session.refreshToken, setSession);\n }\n\n private async refreshSession(\n refreshToken: OAuthRefreshToken | undefined,\n setSession: (session: OAuthSession | null) => Promise<void>\n ): Promise<OAuthAccessToken | null> {\n const log = this.log.child({ op: 'refresh' });\n if (!refreshToken) {\n log.warn(() => ({\n msg: 'clearing the session: it carries no refresh token',\n }));\n await setSession(null);\n return null;\n }\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 });\n const newSession = OAuthSession.fromDto(sessionDto);\n await setSession(newSession);\n log.info(() => ({ msg: 'session renewed' }));\n return newSession.accessToken;\n } catch (error) {\n // The user is logged out by this line, and nothing above sees why:\n // `getAccessToken` answers `null` either way.\n log.failure(\n () => ({ msg: 'refresh failed; clearing the session' }),\n error\n );\n await setSession(null);\n return null;\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 // A failed revoke still leaves the local session invalid, so only strict\n // callers care; anything that is not an HTTP failure is a real bug.\n if (!(err instanceof HttpError) || this.config.strict) {\n throw err;\n }\n this.log.child({ op: 'logout' }).warn(() => ({\n msg: 'the token was not revoked upstream; the local session is cleared regardless',\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(): (\n session: OAuthSession | null\n ) => Promise<void> {\n const sessionStore = this.config.sessionStore;\n const setSession = sessionStore.setSession?.bind(sessionStore);\n if (!setSession) {\n throw new WritableSessionStoreRequiredError();\n }\n return setSession;\n }\n}\n","import type { ServerNamespaceContext } from '@/server/core/server-namespace-context';\nimport * as offersApi from '@/shared/api/frontline/offers';\nimport {\n type InternalLogger,\n internalLogger,\n} from '@/shared/core/observability/internal-logger';\nimport type { OffersNamespace } from '@/shared/core/offers/offers-namespace';\nimport type { ClientCredentialsOAuth } from '@/shared/types/oauth-session';\nimport type { Offer, OfferId } from '@/shared/types/offer';\nimport type { OfferDetail } from '@/shared/types/offer-detail';\nimport type {\n PaginatedResponse,\n PaginationParams,\n} from '@/shared/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 '@/shared/api/frontline/requirements';\nimport type { SharedNamespaceContext } from '@/shared/core/namespace-context';\nimport {\n type RequirementsNamespace,\n RequirementsNamespaceImpl,\n} from '@/shared/core/requirements/requirements-namespace';\nimport type { ClientCredentialsOAuth } from '@/shared/types/oauth-session';\nimport type { OfferId } from '@/shared/types/offer';\nimport type { OfferOptionId } from '@/shared/types/offer-detail';\nimport type { Requirement } from '@/shared/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: SharedNamespaceContext & 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 {\n type ServerAuthNamespace,\n ServerAuthNamespaceImpl,\n type SessionStore,\n} from '@/server/core/server-auth-namespace';\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 {\n API_VERSION,\n PUBLIC_API_BASE_URL,\n} from '@/shared/api/frontline/config';\nimport { NABU_BASE_URL } from '@/shared/api/nabu/config';\nimport {\n type Erc20Namespace,\n Erc20NamespaceImpl,\n} from '@/shared/core/blockchain/erc20/erc20-namespace';\nimport {\n type CoinListTokenSaleNamespace,\n CoinListTokenSaleNamespaceImpl,\n} from '@/shared/core/checkout/coin-list/token-sale-namespace';\nimport {\n type OndoNamespace,\n OndoNamespaceImpl,\n} from '@/shared/core/checkout/ondo/ondo-namespace';\nimport {\n type SuperstateSwapNamespace,\n SuperstateSwapNamespaceImpl,\n} from '@/shared/core/checkout/superstate/swap-namespace';\nimport type { SharedNamespaceContext } from '@/shared/core/namespace-context';\nimport {\n type TokensNamespace,\n TokensNamespaceImpl,\n} from '@/shared/core/tokens/tokens-namespace';\nimport {\n type WalletsNamespace,\n WalletsNamespaceImpl,\n} from '@/shared/core/wallets/wallets-namespace';\nimport type { Config } from '@/shared/types/config';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport type { ClientSecret } from '@/shared/types/oauth';\nimport type { ClientCredentialsOAuth } from '@/shared/types/oauth-session';\n\n/** Buffer in seconds before expiry to consider token expired for refresh. */\nconst ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;\n\nexport interface ServerConfig extends Config {\n readonly clientSecret: ClientSecret;\n readonly sessionStore: SessionStore;\n /** Buffer in seconds before expiry to consider token expired for refresh. */\n readonly accessTokenExpiryBufferSeconds?: number;\n /**\n * Whether SDK will throw an exception in cases it can be silent.\n * For example, if token revokation on logout fails.\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.\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 * 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 tokens: TokensNamespace;\n\n constructor(private readonly _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 // When refresh=true the renewal middleware has received a 401 and wants a\n // fresh token. A read-only store cannot persist a new session, so return\n // null immediately — this tells the middleware to skip the retry rather\n // than re-sending with the same expired token and wasting a round-trip.\n (refresh) =>\n refresh && !this._config.sessionStore.setSession\n ? Promise.resolve(null)\n : this.auth.getAccessToken(),\n logger\n );\n this.auth = new ServerAuthNamespaceImpl(this.api, {\n ..._config,\n accessTokenExpiryBufferSeconds:\n _config.accessTokenExpiryBufferSeconds ??\n ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS,\n strict: _config.strict ?? false,\n });\n\n const ctx: SharedNamespaceContext & ServerNamespaceContext = {\n api: this.api,\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.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 '@/shared/core/observability/pino-logger';\nimport type { Logger, PinoLoggerOptions } from '@/shared/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"],"mappings":"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","names":[]}