infra-sdk-typescript 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,908 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/core/version.ts
6
+ var SDK_VERSION = "0.1.0";
7
+
8
+ // src/core/errors.ts
9
+ var InfraError = class extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = new.target.name;
13
+ Object.setPrototypeOf(this, new.target.prototype);
14
+ }
15
+ };
16
+ var InfraConnectionError = class extends InfraError {
17
+ constructor(message, cause) {
18
+ super(message);
19
+ this.cause = cause;
20
+ }
21
+ cause;
22
+ };
23
+ var InfraConnectionTimeoutError = class extends InfraConnectionError {
24
+ constructor(timeoutMs) {
25
+ super(`Request timed out after ${timeoutMs}ms`);
26
+ }
27
+ };
28
+ var InfraAPIError = class extends InfraError {
29
+ constructor(status, message, errors, body, headers) {
30
+ super(message);
31
+ this.status = status;
32
+ this.errors = errors;
33
+ this.body = body;
34
+ this.headers = headers;
35
+ }
36
+ status;
37
+ errors;
38
+ body;
39
+ headers;
40
+ };
41
+ var BadRequestError = class extends InfraAPIError {
42
+ };
43
+ var AuthenticationError = class extends InfraAPIError {
44
+ };
45
+ var PaymentRequiredError = class extends InfraAPIError {
46
+ };
47
+ var PermissionDeniedError = class extends InfraAPIError {
48
+ };
49
+ var NotFoundError = class extends InfraAPIError {
50
+ };
51
+ var ConflictError = class extends InfraAPIError {
52
+ };
53
+ var UnprocessableEntityError = class extends InfraAPIError {
54
+ };
55
+ var RateLimitError = class extends InfraAPIError {
56
+ constructor(status, message, errors, body, headers, retryAfterSeconds) {
57
+ super(status, message, errors, body, headers);
58
+ this.retryAfterSeconds = retryAfterSeconds;
59
+ }
60
+ retryAfterSeconds;
61
+ };
62
+ var InternalServerError = class extends InfraAPIError {
63
+ };
64
+ var ServiceUnavailableError = class extends InfraAPIError {
65
+ };
66
+ var UnknownAPIError = class extends InfraAPIError {
67
+ };
68
+ function errorFromResponse(status, parsedBody, headers) {
69
+ const body = parsedBody ?? {};
70
+ const message = typeof body.message === "string" ? body.message : `Request failed with status ${status}`;
71
+ const errors = Array.isArray(body.errors) ? body.errors.filter((e) => typeof e === "string") : void 0;
72
+ switch (status) {
73
+ case 400:
74
+ return new BadRequestError(status, message, errors, parsedBody, headers);
75
+ case 401:
76
+ return new AuthenticationError(status, message, errors, parsedBody, headers);
77
+ case 402:
78
+ return new PaymentRequiredError(status, message, errors, parsedBody, headers);
79
+ case 403:
80
+ return new PermissionDeniedError(status, message, errors, parsedBody, headers);
81
+ case 404:
82
+ return new NotFoundError(status, message, errors, parsedBody, headers);
83
+ case 409:
84
+ return new ConflictError(status, message, errors, parsedBody, headers);
85
+ case 422:
86
+ return new UnprocessableEntityError(status, message, errors, parsedBody, headers);
87
+ case 429: {
88
+ const bodyRetryAfter = typeof body.retryAfter === "number" ? body.retryAfter : void 0;
89
+ const headerRetryAfter = headers["retry-after"] !== void 0 ? Number(headers["retry-after"]) : void 0;
90
+ const retryAfterSeconds = bodyRetryAfter ?? (headerRetryAfter !== void 0 && Number.isFinite(headerRetryAfter) ? headerRetryAfter : void 0);
91
+ return new RateLimitError(status, message, errors, parsedBody, headers, retryAfterSeconds);
92
+ }
93
+ case 503:
94
+ return new ServiceUnavailableError(status, message, errors, parsedBody, headers);
95
+ default:
96
+ if (status >= 500) return new InternalServerError(status, message, errors, parsedBody, headers);
97
+ return new UnknownAPIError(status, message, errors, parsedBody, headers);
98
+ }
99
+ }
100
+ var InfraAuthTokenError = class extends InfraError {
101
+ constructor(status, error, errorDescription) {
102
+ super(errorDescription ? `${error}: ${errorDescription}` : error);
103
+ this.status = status;
104
+ this.error = error;
105
+ this.errorDescription = errorDescription;
106
+ }
107
+ status;
108
+ error;
109
+ errorDescription;
110
+ };
111
+ var WebhookSignatureVerificationError = class extends InfraError {
112
+ };
113
+
114
+ // src/core/http-client.ts
115
+ var DEFAULT_TIMEOUT_MS = 3e4;
116
+ var DEFAULT_MAX_RETRIES = 2;
117
+ var HttpClient = class {
118
+ baseURL;
119
+ getAuthHeaders;
120
+ fetchImpl;
121
+ timeoutMs;
122
+ maxRetries;
123
+ defaultHeaders;
124
+ constructor(options) {
125
+ this.baseURL = options.baseURL;
126
+ this.getAuthHeaders = options.getAuthHeaders;
127
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
128
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
129
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
130
+ this.defaultHeaders = options.defaultHeaders ?? {};
131
+ }
132
+ /** Issues a request against the Infra API and returns the unwrapped `data` field of the response envelope. */
133
+ async request(options) {
134
+ const url = buildUrl(this.baseURL, options.path, options.query);
135
+ const timeoutMs = options.timeoutMs ?? this.timeoutMs;
136
+ const retryableOnConnectionError = options.method === "GET" || Boolean(options.idempotencyKey);
137
+ let attempt = 0;
138
+ let lastError;
139
+ while (attempt <= this.maxRetries) {
140
+ attempt++;
141
+ const authHeaders = await this.getAuthHeaders();
142
+ const requestHeaders = {
143
+ Accept: "application/json",
144
+ "User-Agent": `infra-sdk-typescript/${SDK_VERSION}`,
145
+ ...this.defaultHeaders,
146
+ ...authHeaders,
147
+ ...options.headers
148
+ };
149
+ if (options.body !== void 0) {
150
+ requestHeaders["Content-Type"] = "application/json";
151
+ }
152
+ if (options.idempotencyKey) {
153
+ requestHeaders["Idempotency-Key"] = options.idempotencyKey;
154
+ }
155
+ const controller = new AbortController();
156
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
157
+ const onCallerAbort = () => controller.abort();
158
+ if (options.signal) {
159
+ if (options.signal.aborted) controller.abort();
160
+ else options.signal.addEventListener("abort", onCallerAbort, { once: true });
161
+ }
162
+ let response;
163
+ try {
164
+ response = await this.fetchImpl(url, {
165
+ method: options.method,
166
+ headers: requestHeaders,
167
+ body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
168
+ signal: controller.signal
169
+ });
170
+ } catch (err) {
171
+ clearTimeout(timer);
172
+ options.signal?.removeEventListener("abort", onCallerAbort);
173
+ if (options.signal?.aborted) {
174
+ throw new InfraConnectionError("Request aborted by caller", err);
175
+ }
176
+ const connectionError = isAbortError(err) ? new InfraConnectionTimeoutError(timeoutMs) : new InfraConnectionError("Could not reach the Infra API", err);
177
+ if (!retryableOnConnectionError || attempt > this.maxRetries) {
178
+ throw connectionError;
179
+ }
180
+ lastError = connectionError;
181
+ await sleep(backoffDelayMs(attempt));
182
+ continue;
183
+ }
184
+ clearTimeout(timer);
185
+ options.signal?.removeEventListener("abort", onCallerAbort);
186
+ const responseHeaders = headersToObject(response.headers);
187
+ const parsedBody = await parseJsonSafely(response);
188
+ if (response.ok) {
189
+ const envelope = parsedBody;
190
+ return envelope?.data;
191
+ }
192
+ const apiError = errorFromResponse(response.status, parsedBody, responseHeaders);
193
+ const isRetryableStatus = apiError instanceof RateLimitError || apiError instanceof ServiceUnavailableError || apiError instanceof InternalServerError;
194
+ if (!isRetryableStatus || attempt > this.maxRetries) {
195
+ throw apiError;
196
+ }
197
+ lastError = apiError;
198
+ const delayMs = apiError instanceof RateLimitError && apiError.retryAfterSeconds !== void 0 ? apiError.retryAfterSeconds * 1e3 : backoffDelayMs(attempt);
199
+ await sleep(delayMs);
200
+ }
201
+ throw lastError;
202
+ }
203
+ };
204
+ function buildUrl(baseURL, path, query) {
205
+ const base = baseURL.endsWith("/") ? baseURL : `${baseURL}/`;
206
+ const url = new URL(path.replace(/^\//, ""), base);
207
+ if (query) {
208
+ for (const [key, value] of Object.entries(query)) {
209
+ if (value === void 0 || value === null) continue;
210
+ url.searchParams.set(key, String(value));
211
+ }
212
+ }
213
+ return url.toString();
214
+ }
215
+ function headersToObject(headers) {
216
+ const obj = {};
217
+ headers.forEach((value, key) => {
218
+ obj[key.toLowerCase()] = value;
219
+ });
220
+ return obj;
221
+ }
222
+ async function parseJsonSafely(response) {
223
+ const text = await response.text();
224
+ if (!text) return void 0;
225
+ try {
226
+ return JSON.parse(text);
227
+ } catch {
228
+ return void 0;
229
+ }
230
+ }
231
+ function isAbortError(err) {
232
+ return err instanceof Error && err.name === "AbortError";
233
+ }
234
+ function backoffDelayMs(attempt) {
235
+ const base = Math.min(500 * 2 ** (attempt - 1), 8e3);
236
+ const jitter = Math.random() * 250;
237
+ return base + jitter;
238
+ }
239
+ function sleep(ms) {
240
+ return new Promise((resolve) => setTimeout(resolve, ms));
241
+ }
242
+
243
+ // src/core/auth.ts
244
+ var TokenManager = class {
245
+ apiKey;
246
+ baseURL;
247
+ scope;
248
+ fetchImpl;
249
+ clockSkewSeconds;
250
+ maxRetries;
251
+ cached = null;
252
+ pendingRefresh = null;
253
+ constructor(options) {
254
+ this.apiKey = options.apiKey;
255
+ this.baseURL = options.baseURL;
256
+ this.scope = options.scope;
257
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
258
+ this.clockSkewSeconds = options.clockSkewSeconds ?? 30;
259
+ this.maxRetries = options.maxRetries ?? 2;
260
+ }
261
+ /** Returns a valid bearer token, minting or refreshing one only when necessary. */
262
+ async getAccessToken(opts = {}) {
263
+ if (!opts.forceRefresh && this.cached && Date.now() < this.cached.expiresAtMs) {
264
+ return this.cached.accessToken;
265
+ }
266
+ if (this.pendingRefresh) {
267
+ return this.pendingRefresh;
268
+ }
269
+ this.pendingRefresh = this.fetchToken().finally(() => {
270
+ this.pendingRefresh = null;
271
+ });
272
+ return this.pendingRefresh;
273
+ }
274
+ async fetchToken() {
275
+ let attempt = 0;
276
+ for (; ; ) {
277
+ attempt++;
278
+ let response;
279
+ try {
280
+ response = await this.fetchImpl(new URL("/v1/oauth/token", this.baseURL), {
281
+ method: "POST",
282
+ headers: {
283
+ "Content-Type": "application/json",
284
+ Accept: "application/json",
285
+ "User-Agent": `infra-sdk-typescript/${SDK_VERSION}`
286
+ },
287
+ body: JSON.stringify({
288
+ grant_type: "client_credentials",
289
+ client_secret: this.apiKey,
290
+ ...this.scope ? { scope: this.scope } : {}
291
+ })
292
+ });
293
+ } catch (err) {
294
+ throw new InfraConnectionError("Could not reach the Infra API to obtain an access token", err);
295
+ }
296
+ const text = await response.text();
297
+ const parsed = text ? JSON.parse(text) : void 0;
298
+ if (response.ok && parsed && "access_token" in parsed) {
299
+ const expiresAtMs = Date.now() + Math.max(0, parsed.expires_in - this.clockSkewSeconds) * 1e3;
300
+ this.cached = { accessToken: parsed.access_token, expiresAtMs };
301
+ return parsed.access_token;
302
+ }
303
+ const errorBody = parsed ?? { error: "server_error" };
304
+ const retryable = response.status === 429 || response.status === 503;
305
+ if (retryable && attempt <= this.maxRetries) {
306
+ const delayMs = typeof errorBody.retryAfter === "number" ? errorBody.retryAfter * 1e3 : Math.min(500 * 2 ** (attempt - 1), 4e3);
307
+ await sleep2(delayMs);
308
+ continue;
309
+ }
310
+ const fallbackDescription = parsed?.message;
311
+ throw new InfraAuthTokenError(
312
+ response.status,
313
+ errorBody.error ?? (response.status === 429 ? "rate_limited" : "server_error"),
314
+ errorBody.error_description ?? fallbackDescription
315
+ );
316
+ }
317
+ }
318
+ /** Drops the cached token, forcing the next call to mint a fresh one. */
319
+ clearCache() {
320
+ this.cached = null;
321
+ }
322
+ };
323
+ function sleep2(ms) {
324
+ return new Promise((resolve) => setTimeout(resolve, ms));
325
+ }
326
+
327
+ // src/resources/resource.ts
328
+ var APIResource = class {
329
+ constructor(client) {
330
+ this.client = client;
331
+ }
332
+ client;
333
+ };
334
+ function generateIdempotencyKey() {
335
+ return crypto.randomUUID();
336
+ }
337
+
338
+ // src/core/pagination.ts
339
+ var Page = class {
340
+ data;
341
+ nextParams;
342
+ fetchNext;
343
+ constructor(opts) {
344
+ this.data = opts.data;
345
+ this.nextParams = opts.nextParams;
346
+ this.fetchNext = opts.fetchNext;
347
+ }
348
+ hasNextPage() {
349
+ return this.nextParams !== null && this.fetchNext !== null;
350
+ }
351
+ async getNextPage() {
352
+ if (!this.fetchNext || !this.nextParams) {
353
+ throw new Error("This is the last page \u2014 check hasNextPage() before calling getNextPage().");
354
+ }
355
+ return this.fetchNext(this.nextParams);
356
+ }
357
+ /** Walks every page starting with this one, yielding each `Page<T>` in turn. */
358
+ async *iterPages() {
359
+ let page = this;
360
+ yield page;
361
+ while (page.hasNextPage()) {
362
+ page = await page.getNextPage();
363
+ yield page;
364
+ }
365
+ }
366
+ /** Yields every item across all pages, fetching subsequent pages lazily as iteration proceeds. */
367
+ async *[Symbol.asyncIterator]() {
368
+ for await (const page of this.iterPages()) {
369
+ yield* page.data;
370
+ }
371
+ }
372
+ /** Eagerly fetches every remaining page and returns all items as a single array. Use with caution on large collections. */
373
+ async toArray() {
374
+ const items = [];
375
+ for await (const item of this) {
376
+ items.push(item);
377
+ }
378
+ return items;
379
+ }
380
+ };
381
+ function pageFromOffsetResponse(raw, fetchNext) {
382
+ const hasNext = raw.pagination.hasMore && raw.pagination.nextOffset !== null;
383
+ return new Page({
384
+ data: raw.items,
385
+ nextParams: hasNext ? { offset: raw.pagination.nextOffset, limit: raw.pagination.limit } : null,
386
+ fetchNext: hasNext ? fetchNext : null
387
+ });
388
+ }
389
+ function pageFromFlatResponse(raw, fetchNext) {
390
+ const itemsSeenSoFar = (raw.page - 1) * raw.pageSize + raw.items.length;
391
+ const hasNext = itemsSeenSoFar < raw.total;
392
+ return new Page({
393
+ data: raw.items,
394
+ nextParams: hasNext ? { page: raw.page + 1, pageSize: raw.pageSize } : null,
395
+ fetchNext: hasNext ? fetchNext : null
396
+ });
397
+ }
398
+ function pageFromMetaResponse(raw, fetchNext) {
399
+ const hasNext = raw.meta.page < raw.meta.totalPages;
400
+ return new Page({
401
+ data: raw.data,
402
+ nextParams: hasNext ? { page: raw.meta.page + 1, limit: raw.meta.limit } : null,
403
+ fetchNext: hasNext ? fetchNext : null
404
+ });
405
+ }
406
+
407
+ // src/resources/wallet/index.ts
408
+ var WalletResource = class extends APIResource {
409
+ /** Derives one or more new addresses on the business HD tree. Requires `wallet.hd.generate`. */
410
+ async generateWallets(params, overrides = {}) {
411
+ const { idempotencyKey, ...rest } = overrides;
412
+ return this.client.request({
413
+ method: "POST",
414
+ path: "/v1/wallet/hd/generate",
415
+ body: params,
416
+ idempotencyKey: idempotencyKey ?? generateIdempotencyKey(),
417
+ ...rest
418
+ });
419
+ }
420
+ /**
421
+ * Lists derived wallets for the business. Requires `wallet.hd.read`.
422
+ *
423
+ * Note: unlike {@link getTransactionHistory}, the API does not return pagination
424
+ * metadata for this endpoint — it always answers with a plain array sized by `limit`.
425
+ */
426
+ async listWallets(params = {}, overrides = {}) {
427
+ return this.client.request({
428
+ method: "GET",
429
+ path: "/v1/wallet/hd",
430
+ query: params,
431
+ ...overrides
432
+ });
433
+ }
434
+ /** Returns the number of derived wallets, optionally filtered by chain. Requires `wallet.hd.read`. */
435
+ async getWalletCount(params = {}, overrides = {}) {
436
+ return this.client.request({
437
+ method: "GET",
438
+ path: "/v1/wallet/hd/count",
439
+ query: params,
440
+ ...overrides
441
+ });
442
+ }
443
+ /** Reports whether HD wallets are enabled for the business. Requires `wallet.hd.read`. */
444
+ async getHdEnabled(overrides = {}) {
445
+ return this.client.request({
446
+ method: "GET",
447
+ path: "/v1/wallet/hd/enabled",
448
+ ...overrides
449
+ });
450
+ }
451
+ /** Fetches a single wallet by its on-chain address. Requires `wallet.hd.read`. */
452
+ async getWalletByAddress(address, params = {}, overrides = {}) {
453
+ return this.client.request({
454
+ method: "GET",
455
+ path: `/v1/wallet/hd/address/${encodeURIComponent(address)}`,
456
+ query: params,
457
+ ...overrides
458
+ });
459
+ }
460
+ /** Fetches a wallet by its HD `addressIndex`. Requires `wallet.hd.read`. */
461
+ async getWalletByAddressIndex(addressIndex, params = {}, overrides = {}) {
462
+ return this.client.request({
463
+ method: "GET",
464
+ path: `/v1/wallet/hd/address-index/${encodeURIComponent(String(addressIndex))}`,
465
+ query: params,
466
+ ...overrides
467
+ });
468
+ }
469
+ /**
470
+ * Signs (and, where applicable, sponsors and submits) a transaction from a derived
471
+ * wallet. Requires `wallet.hd.sign`.
472
+ *
473
+ * Returns a {@link SignedTransactionResult} for local-only signing (a non-EVM chain
474
+ * with `addressIndex` — nothing broadcast), or a {@link WalletTransaction} for a
475
+ * sponsored sign (async `PENDING` by default, or the settled result with
476
+ * `waitForCompletion: true`).
477
+ */
478
+ async signTransaction(params, overrides = {}) {
479
+ const { idempotencyKey, ...rest } = overrides;
480
+ return this.client.request({
481
+ method: "POST",
482
+ path: "/v1/wallet/hd/sign",
483
+ body: params,
484
+ idempotencyKey: idempotencyKey ?? generateIdempotencyKey(),
485
+ ...rest
486
+ });
487
+ }
488
+ /** Deactivates a wallet so it is no longer used for new operations. Requires `wallet.hd.deactivate`. */
489
+ async deactivateWallet(walletId, params = {}, overrides = {}) {
490
+ return this.client.request({
491
+ method: "POST",
492
+ path: `/v1/wallet/hd/deactivate/${encodeURIComponent(walletId)}`,
493
+ query: params,
494
+ ...overrides
495
+ });
496
+ }
497
+ /** Lists the chains the platform can sign and broadcast on. */
498
+ async getSupportedChains(overrides = {}) {
499
+ return this.client.request({
500
+ method: "GET",
501
+ path: "/v1/wallet/rpc/chains",
502
+ ...overrides
503
+ });
504
+ }
505
+ /**
506
+ * Submits a token (or native) transfer from a derived wallet. Requires
507
+ * `wallet.transfer.create`.
508
+ *
509
+ * On a `422` (transfer reverted after broadcast, `waitForCompletion: true` only), the
510
+ * thrown {@link UnprocessableEntityError} still carries the failed transaction — read it
511
+ * from `error.body` (shaped like `{ data: WalletTransaction }`) if you need the details
512
+ * rather than just the message.
513
+ */
514
+ async initiateTransfer(params, overrides = {}) {
515
+ const { idempotencyKey, ...rest } = overrides;
516
+ return this.client.request({
517
+ method: "POST",
518
+ path: "/v1/wallet/transaction",
519
+ body: params,
520
+ idempotencyKey: idempotencyKey ?? generateIdempotencyKey(),
521
+ ...rest
522
+ });
523
+ }
524
+ /**
525
+ * Returns a paginated history of the business's transactions. Requires
526
+ * `wallet.transfer.read`.
527
+ *
528
+ * ```ts
529
+ * const page = await client.wallet.getTransactionHistory({ status: "COMPLETED" });
530
+ * for await (const tx of page) {
531
+ * console.log(tx.id, tx.status);
532
+ * }
533
+ * ```
534
+ */
535
+ async getTransactionHistory(params = {}, overrides = {}) {
536
+ const fetchPage = (query) => this.client.request({
537
+ method: "GET",
538
+ path: "/v1/wallet/transaction/history",
539
+ query: { ...params, ...query },
540
+ ...overrides
541
+ }).then((raw) => pageFromOffsetResponse(raw, fetchPage));
542
+ return fetchPage({ limit: params.limit, offset: params.offset });
543
+ }
544
+ /**
545
+ * Fetches a single transaction record — the authoritative view of a transfer's outcome.
546
+ * Poll this after an async `202` until `status` is `COMPLETED` or `FAILED`. Requires
547
+ * `wallet.transfer.read`.
548
+ */
549
+ async getTransaction(id, overrides = {}) {
550
+ return this.client.request({
551
+ method: "GET",
552
+ path: `/v1/wallet/transaction/${encodeURIComponent(id)}`,
553
+ ...overrides
554
+ });
555
+ }
556
+ /** Returns the business's spendable USD balance, used to pay gas on mainnet transfers. Requires `wallet.balance.read`. */
557
+ async getBalance(overrides = {}) {
558
+ return this.client.request({
559
+ method: "GET",
560
+ path: "/v1/wallet/balance",
561
+ ...overrides
562
+ });
563
+ }
564
+ };
565
+
566
+ // src/resources/tokenization/index.ts
567
+ var TokenizationResource = class extends APIResource {
568
+ /**
569
+ * Issues a new tokenized asset and deploys it on the target chain. Returns immediately
570
+ * with a `PENDING` token plus a deployment `operationRef` and `txHash` — poll
571
+ * {@link getToken} until `status` is `ACTIVE`. Supply `yieldParams` for a
572
+ * `YIELD_BEARING` token.
573
+ */
574
+ async createToken(params, overrides = {}) {
575
+ return this.client.request({
576
+ method: "POST",
577
+ path: "/v1/tokens",
578
+ body: params,
579
+ ...overrides
580
+ });
581
+ }
582
+ /**
583
+ * Returns a paginated list of tokens for the calling project.
584
+ *
585
+ * ```ts
586
+ * const page = await client.tokenization.listTokens();
587
+ * for await (const token of page) console.log(token.ticker, token.status);
588
+ * ```
589
+ */
590
+ async listTokens(params = {}, overrides = {}) {
591
+ const fetchPage = (query) => this.client.request({
592
+ method: "GET",
593
+ path: "/v1/tokens",
594
+ query: { ...params, ...query },
595
+ ...overrides
596
+ }).then((raw) => pageFromFlatResponse(raw, fetchPage));
597
+ return fetchPage({ page: params.page, pageSize: params.pageSize });
598
+ }
599
+ /** Fetches a single token by id, including its per-chain deployment details. */
600
+ async getToken(id, overrides = {}) {
601
+ return this.client.request({
602
+ method: "GET",
603
+ path: `/v1/tokens/${encodeURIComponent(id)}`,
604
+ ...overrides
605
+ });
606
+ }
607
+ /** Mints new units of a token to a recipient address. */
608
+ async mintToken(params, overrides = {}) {
609
+ return this.client.request({
610
+ method: "POST",
611
+ path: "/v1/tokens/mint",
612
+ body: params,
613
+ ...overrides
614
+ });
615
+ }
616
+ /** Burns units of a token from a holder address. */
617
+ async burnToken(params, overrides = {}) {
618
+ return this.client.request({
619
+ method: "POST",
620
+ path: "/v1/tokens/burn",
621
+ body: params,
622
+ ...overrides
623
+ });
624
+ }
625
+ /** Transfers token units between holder addresses. */
626
+ async transferToken(params, overrides = {}) {
627
+ return this.client.request({
628
+ method: "POST",
629
+ path: "/v1/tokens/transfer",
630
+ body: params,
631
+ ...overrides
632
+ });
633
+ }
634
+ /** Registers (whitelists) a wallet address so it can hold a given token. */
635
+ async registerWallet(params, overrides = {}) {
636
+ return this.client.request({
637
+ method: "POST",
638
+ path: "/v1/tokens/register-wallet",
639
+ body: params,
640
+ ...overrides
641
+ });
642
+ }
643
+ /** Returns the paginated list of holder wallet addresses and balances for a token on a chain. */
644
+ async getTokenHolders(params, overrides = {}) {
645
+ const fetchPage = (query) => this.client.request({
646
+ method: "GET",
647
+ path: "/v1/tokens/holders",
648
+ query: { ...params, ...query },
649
+ ...overrides
650
+ }).then((raw) => pageFromFlatResponse(raw, fetchPage));
651
+ return fetchPage({ page: params.page, pageSize: params.pageSize });
652
+ }
653
+ /** Fetches a single token transaction by its reference. */
654
+ async getTokenTransaction(ref, overrides = {}) {
655
+ return this.client.request({
656
+ method: "GET",
657
+ path: `/v1/tokens/transactions/${encodeURIComponent(ref)}`,
658
+ ...overrides
659
+ });
660
+ }
661
+ /** Returns token transactions, optionally filtered by token. */
662
+ async listTokenTransactions(params = {}, overrides = {}) {
663
+ const fetchPage = (query) => this.client.request({
664
+ method: "GET",
665
+ path: "/v1/tokens/transactions",
666
+ query: { ...params, ...query },
667
+ ...overrides
668
+ }).then((raw) => pageFromFlatResponse(raw, fetchPage));
669
+ return fetchPage({ page: params.page, pageSize: params.pageSize });
670
+ }
671
+ /** Updates the annual yield rate for a yield-bearing token. */
672
+ async updateYield(params, overrides = {}) {
673
+ return this.client.request({
674
+ method: "PATCH",
675
+ path: "/v1/tokens/yield",
676
+ body: params,
677
+ ...overrides
678
+ });
679
+ }
680
+ /** Funds and distributes a yield payout to holders. Unclaimed funds may be reclaimed after `reclaimAfter`. */
681
+ async distributeYield(params, overrides = {}) {
682
+ return this.client.request({
683
+ method: "POST",
684
+ path: "/v1/tokens/yield/distribute",
685
+ body: params,
686
+ ...overrides
687
+ });
688
+ }
689
+ /** Pays the scheduled coupon for a yield-bearing token. Set `pushYield` to push funds rather than let investors claim. */
690
+ async payCoupon(params, overrides = {}) {
691
+ return this.client.request({
692
+ method: "POST",
693
+ path: "/v1/tokens/yield/pay-coupon",
694
+ body: params,
695
+ ...overrides
696
+ });
697
+ }
698
+ /** Claims an investor's yield for a given distribution snapshot. */
699
+ async claimYield(params, overrides = {}) {
700
+ return this.client.request({
701
+ method: "POST",
702
+ path: "/v1/tokens/yield/claim",
703
+ body: params,
704
+ ...overrides
705
+ });
706
+ }
707
+ /** Redeems principal to holders at maturity. Set `pushYield` to push funds rather than let investors claim. */
708
+ async redeemPrincipal(params, overrides = {}) {
709
+ return this.client.request({
710
+ method: "POST",
711
+ path: "/v1/tokens/yield/redeem",
712
+ body: params,
713
+ ...overrides
714
+ });
715
+ }
716
+ };
717
+
718
+ // src/resources/indexer/index.ts
719
+ var IndexerResource = class extends APIResource {
720
+ /**
721
+ * Creates a subscription that streams decoded events for a contract to your webhook.
722
+ * Requires the network's subscribe capability (e.g. `indexer.subscribe.evm`).
723
+ *
724
+ * The response includes `signingSecret` only on creation — store it securely, it is
725
+ * never shown again (see {@link rotateSigningSecret} if you lose it).
726
+ */
727
+ async createSubscription(params, overrides = {}) {
728
+ return this.client.request({
729
+ method: "POST",
730
+ path: "/v1/indexer/subscriptions",
731
+ body: params,
732
+ ...overrides
733
+ });
734
+ }
735
+ /** Lists the project's subscriptions. Requires `indexer.subscription.list`. */
736
+ async listSubscriptions(params = {}, overrides = {}) {
737
+ const fetchPage = (query) => this.client.request({
738
+ method: "GET",
739
+ path: "/v1/indexer/subscriptions",
740
+ query: { ...params, ...query },
741
+ ...overrides
742
+ }).then((raw) => pageFromMetaResponse(raw, fetchPage));
743
+ return fetchPage({ page: params.page, limit: params.limit });
744
+ }
745
+ /** Fetches one subscription. Requires `indexer.subscription.read`. */
746
+ async getSubscription(id, overrides = {}) {
747
+ return this.client.request({
748
+ method: "GET",
749
+ path: `/v1/indexer/subscriptions/${encodeURIComponent(id)}`,
750
+ ...overrides
751
+ });
752
+ }
753
+ /** Updates a subscription's webhook, events, or status. At least one field is required. Requires `indexer.subscription.update`. */
754
+ async updateSubscription(id, params, overrides = {}) {
755
+ return this.client.request({
756
+ method: "PATCH",
757
+ path: `/v1/indexer/subscriptions/${encodeURIComponent(id)}`,
758
+ body: params,
759
+ ...overrides
760
+ });
761
+ }
762
+ /** Soft-deletes a subscription (`status` becomes `"deleted"`; deliveries stop). Requires `indexer.subscription.delete`. */
763
+ async deleteSubscription(id, overrides = {}) {
764
+ return this.client.request({
765
+ method: "DELETE",
766
+ path: `/v1/indexer/subscriptions/${encodeURIComponent(id)}`,
767
+ ...overrides
768
+ });
769
+ }
770
+ /**
771
+ * Generates a new signing secret and invalidates the old one. Requires
772
+ * `indexer.subscription.rotate_secret`. The response includes the new `signingSecret`
773
+ * once — store it immediately.
774
+ */
775
+ async rotateSigningSecret(id, overrides = {}) {
776
+ return this.client.request({
777
+ method: "POST",
778
+ path: `/v1/indexer/subscriptions/${encodeURIComponent(id)}/rotate-secret`,
779
+ ...overrides
780
+ });
781
+ }
782
+ /** Returns the catalog of subscribable event keys. Requires `indexer.subscription.list`. */
783
+ async listEventCatalog(params = {}, overrides = {}) {
784
+ return this.client.request({
785
+ method: "GET",
786
+ path: "/v1/indexer/events",
787
+ query: params,
788
+ ...overrides
789
+ });
790
+ }
791
+ };
792
+
793
+ // src/client.ts
794
+ var DEFAULT_BASE_URL = "https://api.withconvexity.com";
795
+ var InfraClient = class {
796
+ wallet;
797
+ tokenization;
798
+ indexer;
799
+ http;
800
+ auth;
801
+ constructor(options) {
802
+ if (!options.apiKey) {
803
+ throw new Error(
804
+ "InfraClient requires an `apiKey`. Request test/live keys from infra@withconvexity.com."
805
+ );
806
+ }
807
+ const baseURL = options.baseURL ?? DEFAULT_BASE_URL;
808
+ this.auth = new TokenManager({
809
+ apiKey: options.apiKey,
810
+ baseURL,
811
+ scope: options.scope,
812
+ fetch: options.fetch
813
+ });
814
+ this.http = new HttpClient({
815
+ baseURL,
816
+ fetch: options.fetch,
817
+ timeoutMs: options.timeoutMs,
818
+ maxRetries: options.maxRetries,
819
+ getAuthHeaders: async () => ({ Authorization: `Bearer ${await this.auth.getAccessToken()}` })
820
+ });
821
+ this.wallet = new WalletResource(this);
822
+ this.tokenization = new TokenizationResource(this);
823
+ this.indexer = new IndexerResource(this);
824
+ }
825
+ /**
826
+ * Low-level escape hatch that issues a raw request against the Infra API using this
827
+ * client's auth, retry, and timeout configuration. The typed resource methods
828
+ * (`client.wallet.*`, `client.tokenization.*`, `client.indexer.*`) are built on top of
829
+ * this and should be preferred — reach for this only to call an endpoint this SDK
830
+ * version doesn't wrap yet.
831
+ */
832
+ async request(options) {
833
+ try {
834
+ return await this.http.request(options);
835
+ } catch (err) {
836
+ if (err instanceof AuthenticationError) {
837
+ await this.auth.getAccessToken({ forceRefresh: true });
838
+ return await this.http.request(options);
839
+ }
840
+ throw err;
841
+ }
842
+ }
843
+ };
844
+ var DEFAULT_TOLERANCE_SECONDS = 300;
845
+ function verifyWalletWebhookSignature(options) {
846
+ const { payload, timestampHeader, signatureHeader, secret, toleranceSeconds = DEFAULT_TOLERANCE_SECONDS } = options;
847
+ const timestamp = Number(timestampHeader);
848
+ if (!Number.isFinite(timestamp)) {
849
+ throw new WebhookSignatureVerificationError("Missing or invalid x-wallet-timestamp header");
850
+ }
851
+ const ageSeconds = Math.abs(Date.now() / 1e3 - timestamp);
852
+ if (ageSeconds > toleranceSeconds) {
853
+ throw new WebhookSignatureVerificationError(
854
+ `Webhook timestamp is ${Math.round(ageSeconds)}s old, outside the allowed tolerance of ${toleranceSeconds}s`
855
+ );
856
+ }
857
+ const rawBody = typeof payload === "string" ? payload : payload.toString("utf8");
858
+ const expected = crypto.createHmac("sha256", secret).update(`${timestampHeader}.${rawBody}`).digest("hex");
859
+ if (!constantTimeEqual(expected, signatureHeader)) {
860
+ throw new WebhookSignatureVerificationError(
861
+ "Signature mismatch \u2014 the payload may have been tampered with, or the secret is incorrect"
862
+ );
863
+ }
864
+ }
865
+ function verifyIndexerWebhookSignature(options) {
866
+ const { payload, signatureHeader, secret } = options;
867
+ const rawBody = typeof payload === "string" ? payload : payload.toString("utf8");
868
+ const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
869
+ if (!constantTimeEqual(expected, signatureHeader)) {
870
+ throw new WebhookSignatureVerificationError(
871
+ "Signature mismatch \u2014 the payload may have been tampered with, or the secret is incorrect"
872
+ );
873
+ }
874
+ }
875
+ function constantTimeEqual(expectedHex, actualHex) {
876
+ const expected = Buffer.from(expectedHex, "utf8");
877
+ const actual = Buffer.from(actualHex, "utf8");
878
+ if (expected.length !== actual.length) return false;
879
+ return crypto.timingSafeEqual(expected, actual);
880
+ }
881
+
882
+ exports.AuthenticationError = AuthenticationError;
883
+ exports.BadRequestError = BadRequestError;
884
+ exports.ConflictError = ConflictError;
885
+ exports.IndexerResource = IndexerResource;
886
+ exports.InfraAPIError = InfraAPIError;
887
+ exports.InfraAuthTokenError = InfraAuthTokenError;
888
+ exports.InfraClient = InfraClient;
889
+ exports.InfraConnectionError = InfraConnectionError;
890
+ exports.InfraConnectionTimeoutError = InfraConnectionTimeoutError;
891
+ exports.InfraError = InfraError;
892
+ exports.InternalServerError = InternalServerError;
893
+ exports.NotFoundError = NotFoundError;
894
+ exports.Page = Page;
895
+ exports.PaymentRequiredError = PaymentRequiredError;
896
+ exports.PermissionDeniedError = PermissionDeniedError;
897
+ exports.RateLimitError = RateLimitError;
898
+ exports.ServiceUnavailableError = ServiceUnavailableError;
899
+ exports.TokenizationResource = TokenizationResource;
900
+ exports.UnknownAPIError = UnknownAPIError;
901
+ exports.UnprocessableEntityError = UnprocessableEntityError;
902
+ exports.WalletResource = WalletResource;
903
+ exports.WebhookSignatureVerificationError = WebhookSignatureVerificationError;
904
+ exports.generateIdempotencyKey = generateIdempotencyKey;
905
+ exports.verifyIndexerWebhookSignature = verifyIndexerWebhookSignature;
906
+ exports.verifyWalletWebhookSignature = verifyWalletWebhookSignature;
907
+ //# sourceMappingURL=index.cjs.map
908
+ //# sourceMappingURL=index.cjs.map