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