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 +21 -0
- package/README.md +249 -0
- package/dist/index.cjs +908 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +912 -0
- package/dist/index.d.ts +912 -0
- package/dist/index.js +882 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
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).
|