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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Convexity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # infra-sdk-typescript
2
+
3
+ Official TypeScript SDK for the [Convexity Infra API](https://docs.withconvexity.com) —
4
+ **Wallet**, **Tokenization**, and **Blockchain Events (Indexer)**.
5
+
6
+ - Fully typed — every request and response shape is modeled, including the three
7
+ different pagination envelopes the API uses.
8
+ - Handles auth for you — exchange your API key once; the client mints, caches, and
9
+ refreshes OAuth bearer tokens automatically.
10
+ - Automatic retries with backoff for rate limits (`429`, honoring `retryAfter`) and
11
+ upstream outages (`503`), plus safe retries on connection failures for idempotent
12
+ requests.
13
+ - Auto-generated `Idempotency-Key` headers on value-moving wallet operations, with the
14
+ option to supply your own.
15
+ - A typed error hierarchy (`RateLimitError`, `NotFoundError`, `PaymentRequiredError`, …)
16
+ instead of parsing status codes yourself.
17
+ - Webhook signature verification for both the Wallet outcome webhook and Indexer event
18
+ deliveries.
19
+ - Zero runtime dependencies — built on native `fetch` and `node:crypto`. Ships dual
20
+ ESM/CJS builds with full `.d.ts` types.
21
+
22
+ ## Requirements
23
+
24
+ - Node.js 18 or later.
25
+ - A Convexity Infra API key. There's no self-service signup — email
26
+ [infra@withconvexity.com](mailto:infra@withconvexity.com) with your business details to
27
+ get a test key (`sk_test_...`) and a live key (`sk_live_...`).
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ npm install infra-sdk-typescript
33
+ ```
34
+
35
+ ## Quickstart
36
+
37
+ ```ts
38
+ import { InfraClient } from "infra-sdk-typescript";
39
+
40
+ const client = new InfraClient({ apiKey: process.env.INFRA_API_KEY! });
41
+
42
+ const balance = await client.wallet.getBalance();
43
+ console.log(`Business balance: $${balance.balance}`);
44
+ ```
45
+
46
+ The client exchanges your API key for a short-lived OAuth bearer token on first use
47
+ (`POST /v1/oauth/token`), caches it, and refreshes it before it expires (and once, on an
48
+ unexpected `401`) — you never handle tokens directly.
49
+
50
+ More end-to-end examples live in [`examples/`](./examples):
51
+
52
+ - [`basic-usage.ts`](./examples/basic-usage.ts) — derive a wallet, transfer, poll for completion.
53
+ - [`pagination.ts`](./examples/pagination.ts) — the three ways to consume a paginated list.
54
+ - [`tokenization-yield-lifecycle.ts`](./examples/tokenization-yield-lifecycle.ts) — issue a
55
+ yield-bearing token, mint, pay a coupon, redeem principal.
56
+ - [`indexer-subscription.ts`](./examples/indexer-subscription.ts) — subscribe to on-chain
57
+ events and manage the subscription.
58
+ - [`webhook-verification.ts`](./examples/webhook-verification.ts) — verify both webhook
59
+ signature schemes in a plain `node:http` server.
60
+
61
+ ## Resources
62
+
63
+ The client exposes one namespace per service:
64
+
65
+ | Namespace | Service | Docs |
66
+ |---|---|---|
67
+ | `client.wallet` | HD wallets, on-chain transfers, sponsored signing, USD gas balance | [Wallet overview](https://docs.withconvexity.com/api-reference/wallet/overview) |
68
+ | `client.tokenization` | Issue, mint/burn/transfer tokenized assets; yield lifecycle | [Tokenization overview](https://docs.withconvexity.com/api-reference/tokenization/overview) |
69
+ | `client.indexer` | Subscribe to decoded on-chain events, delivered to your webhook | [Blockchain Events overview](https://docs.withconvexity.com/api-reference/indexer/overview) |
70
+
71
+ Every method mirrors the API 1:1 (see each resource's JSDoc, or the type definitions, for
72
+ the full parameter/return shape). A few highlights:
73
+
74
+ ### Async transfers
75
+
76
+ [`initiateTransfer`](./src/resources/wallet/index.ts) and
77
+ [`signTransaction`](./src/resources/wallet/index.ts) are asynchronous by default: they
78
+ return a `PENDING` transaction immediately (HTTP `202`), and the on-chain execution
79
+ continues in the background.
80
+
81
+ ```ts
82
+ const transfer = await client.wallet.initiateTransfer({
83
+ fromAddress: 12, // HD addressIndex, or a full "0x..." address
84
+ toAddress: "0x2222222222222222222222222222222222222222",
85
+ amount: "1000000",
86
+ chainType: "EVM",
87
+ chainId: 8453,
88
+ });
89
+
90
+ // Poll until it settles...
91
+ let final = await client.wallet.getTransaction(transfer.id);
92
+ while (final.status === "PENDING" || final.status === "PROCESSING") {
93
+ await new Promise((r) => setTimeout(r, 2000));
94
+ final = await client.wallet.getTransaction(transfer.id);
95
+ }
96
+
97
+ // ...or receive the outcome at a webhook instead, by passing webhookUrl/webhookSecret
98
+ // on the initiate call, and verifying deliveries with `verifyWalletWebhookSignature`.
99
+ ```
100
+
101
+ Pass `waitForCompletion: true` for the legacy synchronous behavior, which holds the
102
+ request open for the settled result instead.
103
+
104
+ ### Pagination
105
+
106
+ The API uses three different pagination envelopes depending on the service. This SDK
107
+ normalizes all of them into one `Page<T>` type:
108
+
109
+ ```ts
110
+ // Lazily iterate every item, fetching subsequent pages as needed:
111
+ for await (const token of await client.tokenization.listTokens()) {
112
+ console.log(token.ticker);
113
+ }
114
+
115
+ // Or walk page by page:
116
+ let page = await client.wallet.getTransactionHistory({ status: "COMPLETED" });
117
+ for (;;) {
118
+ for (const tx of page.data) handle(tx);
119
+ if (!page.hasNextPage()) break;
120
+ page = await page.getNextPage();
121
+ }
122
+
123
+ // Or eagerly collect everything (fine for small collections):
124
+ const holders = await (await client.tokenization.getTokenHolders({ tokenId })).toArray();
125
+ ```
126
+
127
+ ### Idempotency
128
+
129
+ Wallet's value-moving endpoints (`initiateTransfer`, `signTransaction`, `generateWallets`)
130
+ require an `Idempotency-Key` header. The SDK generates a UUIDv4 for you automatically; pass
131
+ your own to control retries across separate SDK calls (e.g. after a process restart):
132
+
133
+ ```ts
134
+ await client.wallet.initiateTransfer(params, { idempotencyKey: "my-own-key" });
135
+ ```
136
+
137
+ ### Errors
138
+
139
+ Every failure is a subclass of `InfraError`. HTTP failures are subclasses of
140
+ `InfraAPIError`, matching the API's status code table:
141
+
142
+ ```ts
143
+ import { RateLimitError, PaymentRequiredError, UnprocessableEntityError, InfraAPIError } from "infra-sdk-typescript";
144
+
145
+ try {
146
+ await client.wallet.initiateTransfer(params);
147
+ } catch (err) {
148
+ if (err instanceof RateLimitError) {
149
+ console.log(`Throttled — retry after ${err.retryAfterSeconds}s`);
150
+ } else if (err instanceof PaymentRequiredError) {
151
+ console.log("Insufficient gas balance");
152
+ } else if (err instanceof UnprocessableEntityError) {
153
+ // waitForCompletion: true transfers that revert on-chain still carry the failed
154
+ // transaction — read it off the raw response body if you need the details.
155
+ console.log((err.body as { data?: unknown }).data);
156
+ } else if (err instanceof InfraAPIError) {
157
+ console.log(err.status, err.message, err.errors);
158
+ }
159
+ }
160
+ ```
161
+
162
+ `429` and `503` responses are retried automatically (2 retries by default); everything
163
+ else is thrown immediately since retrying without changing the request won't help.
164
+
165
+ ### Retries, timeouts, and connection safety
166
+
167
+ - `429` and `503` are retried automatically — the server didn't execute the request, so
168
+ this is always safe. `429` retries honor the response's `retryAfter`.
169
+ - A connection failure (timeout, DNS, TLS, aborted socket) is ambiguous — the request may
170
+ have already run server-side. The SDK only retries these for `GET` requests or requests
171
+ carrying an `Idempotency-Key`, where a retry is provably safe.
172
+ - Configure globally via the constructor, or per call:
173
+
174
+ ```ts
175
+ const client = new InfraClient({
176
+ apiKey: process.env.INFRA_API_KEY!,
177
+ timeoutMs: 30_000, // default
178
+ maxRetries: 2, // default
179
+ });
180
+
181
+ await client.wallet.getBalance({ timeoutMs: 5_000, signal: myAbortController.signal });
182
+ ```
183
+
184
+ ### Webhooks
185
+
186
+ ```ts
187
+ import { verifyWalletWebhookSignature, verifyIndexerWebhookSignature } from "infra-sdk-typescript";
188
+
189
+ // Wallet outcome webhooks (wallet.transfer.completed / .failed, wallet.sign.completed / .failed):
190
+ // HMAC-SHA256("<timestamp>.<rawBody>"), with a 5-minute replay-protection window by default.
191
+ verifyWalletWebhookSignature({
192
+ payload: rawBody, // the exact bytes received — do not JSON.parse + re-stringify first
193
+ timestampHeader: req.headers["x-wallet-timestamp"],
194
+ signatureHeader: req.headers["x-wallet-signature"],
195
+ secret: webhookSecret, // the value you passed as `webhookSecret` on the originating call
196
+ });
197
+
198
+ // Indexer (Blockchain Events) deliveries: HMAC-SHA256(rawBody), no timestamp component.
199
+ verifyIndexerWebhookSignature({
200
+ payload: rawBody,
201
+ signatureHeader: req.headers["x-indexer-signature"],
202
+ secret: signingSecret, // returned once, on subscription creation or secret rotation
203
+ });
204
+ ```
205
+
206
+ Both throw `WebhookSignatureVerificationError` on failure — see
207
+ [`examples/webhook-verification.ts`](./examples/webhook-verification.ts) for a full
208
+ `node:http` handler.
209
+
210
+ ### Escape hatch
211
+
212
+ Every resource method is a thin wrapper over `client.request()`, which carries the same
213
+ auth, retry, and timeout behavior. Use it directly for any endpoint this SDK version
214
+ doesn't wrap yet:
215
+
216
+ ```ts
217
+ const data = await client.request<{ enabled: boolean }>({
218
+ method: "GET",
219
+ path: "/v1/wallet/hd/enabled",
220
+ });
221
+ ```
222
+
223
+ ## Configuration reference
224
+
225
+ ```ts
226
+ new InfraClient({
227
+ apiKey: string; // required — sk_live_... or sk_test_...
228
+ baseURL?: string; // default "https://api.withconvexity.com"
229
+ scope?: string; // space-separated OAuth scopes; omit for full access
230
+ timeoutMs?: number; // default 30000
231
+ maxRetries?: number; // default 2
232
+ fetch?: typeof fetch; // override for testing
233
+ });
234
+ ```
235
+
236
+ ## Development
237
+
238
+ ```bash
239
+ npm install
240
+ npm run typecheck # tsc --noEmit
241
+ npm run lint # eslint
242
+ npm test # vitest
243
+ npm run build # tsup -> dist/ (ESM + CJS + .d.ts)
244
+ npm run ci # all of the above, in order
245
+ ```
246
+
247
+ ## License
248
+
249
+ MIT — see [LICENSE](./LICENSE).