@remitmd/sdk 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.
Files changed (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -0
  3. package/dist/a2a.d.ts +137 -0
  4. package/dist/a2a.d.ts.map +1 -0
  5. package/dist/a2a.js +121 -0
  6. package/dist/a2a.js.map +1 -0
  7. package/dist/client.d.ts +41 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +81 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/errors.d.ts +108 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +218 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/http.d.ts +23 -0
  16. package/dist/http.d.ts.map +1 -0
  17. package/dist/http.js +150 -0
  18. package/dist/http.js.map +1 -0
  19. package/dist/index.d.ts +18 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +21 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/integrations/vercel-ai.d.ts +44 -0
  24. package/dist/integrations/vercel-ai.d.ts.map +1 -0
  25. package/dist/integrations/vercel-ai.js +175 -0
  26. package/dist/integrations/vercel-ai.js.map +1 -0
  27. package/dist/models/bounty.d.ts +22 -0
  28. package/dist/models/bounty.d.ts.map +1 -0
  29. package/dist/models/bounty.js +2 -0
  30. package/dist/models/bounty.js.map +1 -0
  31. package/dist/models/common.d.ts +78 -0
  32. package/dist/models/common.d.ts.map +1 -0
  33. package/dist/models/common.js +3 -0
  34. package/dist/models/common.js.map +1 -0
  35. package/dist/models/deposit.d.ts +13 -0
  36. package/dist/models/deposit.d.ts.map +1 -0
  37. package/dist/models/deposit.js +2 -0
  38. package/dist/models/deposit.js.map +1 -0
  39. package/dist/models/escrow.d.ts +16 -0
  40. package/dist/models/escrow.d.ts.map +1 -0
  41. package/dist/models/escrow.js +2 -0
  42. package/dist/models/escrow.js.map +1 -0
  43. package/dist/models/index.d.ts +9 -0
  44. package/dist/models/index.d.ts.map +1 -0
  45. package/dist/models/index.js +9 -0
  46. package/dist/models/index.js.map +1 -0
  47. package/dist/models/invoice.d.ts +30 -0
  48. package/dist/models/invoice.d.ts.map +1 -0
  49. package/dist/models/invoice.js +2 -0
  50. package/dist/models/invoice.js.map +1 -0
  51. package/dist/models/reputation.d.ts +7 -0
  52. package/dist/models/reputation.d.ts.map +1 -0
  53. package/dist/models/reputation.js +2 -0
  54. package/dist/models/reputation.js.map +1 -0
  55. package/dist/models/stream.d.ts +15 -0
  56. package/dist/models/stream.d.ts.map +1 -0
  57. package/dist/models/stream.js +2 -0
  58. package/dist/models/stream.js.map +1 -0
  59. package/dist/models/tab.d.ts +21 -0
  60. package/dist/models/tab.d.ts.map +1 -0
  61. package/dist/models/tab.js +2 -0
  62. package/dist/models/tab.js.map +1 -0
  63. package/dist/provider.d.ts +135 -0
  64. package/dist/provider.d.ts.map +1 -0
  65. package/dist/provider.js +218 -0
  66. package/dist/provider.js.map +1 -0
  67. package/dist/signer.d.ts +31 -0
  68. package/dist/signer.d.ts.map +1 -0
  69. package/dist/signer.js +35 -0
  70. package/dist/signer.js.map +1 -0
  71. package/dist/testing/local.d.ts +31 -0
  72. package/dist/testing/local.d.ts.map +1 -0
  73. package/dist/testing/local.js +100 -0
  74. package/dist/testing/local.js.map +1 -0
  75. package/dist/testing/mock.d.ts +95 -0
  76. package/dist/testing/mock.d.ts.map +1 -0
  77. package/dist/testing/mock.js +407 -0
  78. package/dist/testing/mock.js.map +1 -0
  79. package/dist/wallet.d.ts +162 -0
  80. package/dist/wallet.d.ts.map +1 -0
  81. package/dist/wallet.js +365 -0
  82. package/dist/wallet.js.map +1 -0
  83. package/dist/x402.d.ts +78 -0
  84. package/dist/x402.d.ts.map +1 -0
  85. package/dist/x402.js +151 -0
  86. package/dist/x402.js.map +1 -0
  87. package/eslint.config.js +27 -0
  88. package/package.json +39 -0
  89. package/src/a2a.ts +241 -0
  90. package/src/client.ts +104 -0
  91. package/src/errors.ts +261 -0
  92. package/src/http.ts +190 -0
  93. package/src/index.ts +94 -0
  94. package/src/integrations/vercel-ai.ts +213 -0
  95. package/src/models/bounty.ts +23 -0
  96. package/src/models/common.ts +106 -0
  97. package/src/models/deposit.ts +13 -0
  98. package/src/models/escrow.ts +16 -0
  99. package/src/models/index.ts +8 -0
  100. package/src/models/invoice.ts +32 -0
  101. package/src/models/reputation.ts +7 -0
  102. package/src/models/stream.ts +15 -0
  103. package/src/models/tab.ts +22 -0
  104. package/src/provider.ts +281 -0
  105. package/src/signer.ts +70 -0
  106. package/src/testing/local.ts +118 -0
  107. package/src/testing/mock.ts +507 -0
  108. package/src/wallet.ts +546 -0
  109. package/src/x402.ts +202 -0
  110. package/tests/acceptance/bounty.test.ts +82 -0
  111. package/tests/acceptance/deposit.test.ts +70 -0
  112. package/tests/acceptance/direct.test.ts +53 -0
  113. package/tests/acceptance/escrow.test.ts +67 -0
  114. package/tests/acceptance/setup.ts +113 -0
  115. package/tests/acceptance/stream.test.ts +98 -0
  116. package/tests/acceptance/tab.test.ts +108 -0
  117. package/tests/acceptance/x402.test.ts +140 -0
  118. package/tests/compliance/auth.ts +69 -0
  119. package/tests/compliance/escrows.ts +96 -0
  120. package/tests/compliance/helpers.ts +90 -0
  121. package/tests/compliance/payments.ts +69 -0
  122. package/tests/compliance/tabs.ts +52 -0
  123. package/tests/test_a2a.ts +151 -0
  124. package/tests/test_errors.ts +80 -0
  125. package/tests/test_golden_vectors.ts +162 -0
  126. package/tests/test_integrations.ts +115 -0
  127. package/tests/test_mock.ts +217 -0
  128. package/tests/test_permit.ts +216 -0
  129. package/tests/test_provider.ts +304 -0
  130. package/tests/test_wallet.ts +108 -0
  131. package/tests/test_x402.ts +302 -0
  132. package/tsconfig.json +19 -0
package/src/errors.ts ADDED
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Typed error classes for remit.md error codes.
3
+ *
4
+ * Every API error code maps to a specific class so callers can handle precisely:
5
+ *
6
+ * try {
7
+ * await wallet.pay(invoice);
8
+ * } catch (e) {
9
+ * if (e instanceof InsufficientBalanceError) {
10
+ * await wallet.requestTestnetFunds();
11
+ * }
12
+ * }
13
+ */
14
+
15
+ export class RemitError extends Error {
16
+ readonly code: string;
17
+ readonly httpStatus: number;
18
+
19
+ constructor(message: string, code = "UNKNOWN_ERROR", httpStatus = 500) {
20
+ super(message);
21
+ this.name = this.constructor.name;
22
+ this.code = code;
23
+ this.httpStatus = httpStatus;
24
+ }
25
+ }
26
+
27
+ // ─── Auth errors ─────────────────────────────────────────────────────────────
28
+
29
+ export class InvalidSignatureError extends RemitError {
30
+ constructor(msg = "Invalid EIP-712 signature.") {
31
+ super(msg, "INVALID_SIGNATURE", 401);
32
+ }
33
+ }
34
+
35
+ export class NonceReusedError extends RemitError {
36
+ constructor(msg = "Nonce has already been used.") {
37
+ super(msg, "NONCE_REUSED", 401);
38
+ }
39
+ }
40
+
41
+ export class TimestampExpiredError extends RemitError {
42
+ constructor(msg = "Request timestamp has expired.") {
43
+ super(msg, "TIMESTAMP_EXPIRED", 401);
44
+ }
45
+ }
46
+
47
+ export class UnauthorizedError extends RemitError {
48
+ constructor(msg = "Authentication required.") {
49
+ super(msg, "UNAUTHORIZED", 401);
50
+ }
51
+ }
52
+
53
+ // ─── Balance / funds ──────────────────────────────────────────────────────────
54
+
55
+ export class InsufficientBalanceError extends RemitError {
56
+ constructor(msg = "Wallet does not have enough USDC for this transaction + fee.") {
57
+ super(msg, "INSUFFICIENT_BALANCE", 402);
58
+ }
59
+ }
60
+
61
+ export class BelowMinimumError extends RemitError {
62
+ constructor(msg = "Transaction amount is below $0.01 minimum.") {
63
+ super(msg, "BELOW_MINIMUM", 400);
64
+ }
65
+ }
66
+
67
+ // ─── Escrow errors ────────────────────────────────────────────────────────────
68
+
69
+ export class EscrowNotFoundError extends RemitError {
70
+ constructor(msg = "Escrow not found.") {
71
+ super(msg, "ESCROW_NOT_FOUND", 404);
72
+ }
73
+ }
74
+
75
+ export class EscrowAlreadyFundedError extends RemitError {
76
+ constructor(msg = "This invoice already has a funded escrow.") {
77
+ super(msg, "ESCROW_ALREADY_FUNDED", 409);
78
+ }
79
+ }
80
+
81
+ export class EscrowExpiredError extends RemitError {
82
+ constructor(msg = "Escrow has expired.") {
83
+ super(msg, "ESCROW_EXPIRED", 410);
84
+ }
85
+ }
86
+
87
+ // ─── Invoice errors ───────────────────────────────────────────────────────────
88
+
89
+ export class InvalidInvoiceError extends RemitError {
90
+ constructor(msg = "Invoice is malformed or invalid.") {
91
+ super(msg, "INVALID_INVOICE", 400);
92
+ }
93
+ }
94
+
95
+ export class DuplicateInvoiceError extends RemitError {
96
+ constructor(msg = "An invoice with this ID already exists.") {
97
+ super(msg, "DUPLICATE_INVOICE", 409);
98
+ }
99
+ }
100
+
101
+ export class SelfPaymentError extends RemitError {
102
+ constructor(msg = "Cannot pay yourself.") {
103
+ super(msg, "SELF_PAYMENT", 400);
104
+ }
105
+ }
106
+
107
+ export class InvalidPaymentTypeError extends RemitError {
108
+ constructor(msg = "Payment type is not valid for this invoice.") {
109
+ super(msg, "INVALID_PAYMENT_TYPE", 400);
110
+ }
111
+ }
112
+
113
+ // ─── Tab errors ───────────────────────────────────────────────────────────────
114
+
115
+ export class TabDepletedError extends RemitError {
116
+ constructor(msg = "Tab has reached its spending limit.") {
117
+ super(msg, "TAB_DEPLETED", 402);
118
+ }
119
+ }
120
+
121
+ export class TabExpiredError extends RemitError {
122
+ constructor(msg = "Tab has expired.") {
123
+ super(msg, "TAB_EXPIRED", 410);
124
+ }
125
+ }
126
+
127
+ export class TabNotFoundError extends RemitError {
128
+ constructor(msg = "Tab not found.") {
129
+ super(msg, "TAB_NOT_FOUND", 404);
130
+ }
131
+ }
132
+
133
+ // ─── Stream errors ────────────────────────────────────────────────────────────
134
+
135
+ export class StreamNotFoundError extends RemitError {
136
+ constructor(msg = "Stream not found.") {
137
+ super(msg, "STREAM_NOT_FOUND", 404);
138
+ }
139
+ }
140
+
141
+ export class RateExceedsCapError extends RemitError {
142
+ constructor(msg = "Streaming rate exceeds the maximum allowed.") {
143
+ super(msg, "RATE_EXCEEDS_CAP", 422);
144
+ }
145
+ }
146
+
147
+ // ─── Bounty errors ────────────────────────────────────────────────────────────
148
+
149
+ export class BountyExpiredError extends RemitError {
150
+ constructor(msg = "Bounty has expired.") {
151
+ super(msg, "BOUNTY_EXPIRED", 410);
152
+ }
153
+ }
154
+
155
+ export class BountyClaimedError extends RemitError {
156
+ constructor(msg = "Bounty has already been awarded.") {
157
+ super(msg, "BOUNTY_CLAIMED", 409);
158
+ }
159
+ }
160
+
161
+ export class BountyMaxAttemptsError extends RemitError {
162
+ constructor(msg = "Bounty has reached maximum submission attempts.") {
163
+ super(msg, "BOUNTY_MAX_ATTEMPTS", 422);
164
+ }
165
+ }
166
+
167
+ export class BountyNotFoundError extends RemitError {
168
+ constructor(msg = "Bounty not found.") {
169
+ super(msg, "BOUNTY_NOT_FOUND", 404);
170
+ }
171
+ }
172
+
173
+ // ─── Chain errors ─────────────────────────────────────────────────────────────
174
+
175
+ export class ChainMismatchError extends RemitError {
176
+ constructor(msg = "Invoice chain does not match wallet chain.") {
177
+ super(msg, "CHAIN_MISMATCH", 409);
178
+ }
179
+ }
180
+
181
+ export class ChainUnsupportedError extends RemitError {
182
+ constructor(msg = "This chain is not supported.") {
183
+ super(msg, "CHAIN_UNSUPPORTED", 422);
184
+ }
185
+ }
186
+
187
+ // ─── Rate limiting ────────────────────────────────────────────────────────────
188
+
189
+ export class RateLimitedError extends RemitError {
190
+ constructor(msg = "Rate limit exceeded. Try again later.") {
191
+ super(msg, "RATE_LIMITED", 429);
192
+ }
193
+ }
194
+
195
+ // ─── Cancellation errors ──────────────────────────────────────────────────────
196
+
197
+ export class CancelBlockedClaimStartError extends RemitError {
198
+ constructor(msg = "Cannot cancel after claim start.") {
199
+ super(msg, "CANCEL_BLOCKED_CLAIM_START", 409);
200
+ }
201
+ }
202
+
203
+ export class CancelBlockedEvidenceError extends RemitError {
204
+ constructor(msg = "Cannot cancel while evidence is pending review.") {
205
+ super(msg, "CANCEL_BLOCKED_EVIDENCE", 409);
206
+ }
207
+ }
208
+
209
+ // ─── Protocol errors ──────────────────────────────────────────────────────────
210
+
211
+ export class VersionMismatchError extends RemitError {
212
+ constructor(msg = "SDK version is not compatible with this API version.") {
213
+ super(msg, "VERSION_MISMATCH", 422);
214
+ }
215
+ }
216
+
217
+ export class NetworkError extends RemitError {
218
+ constructor(msg = "Network request failed.") {
219
+ super(msg, "NETWORK_ERROR", 503);
220
+ }
221
+ }
222
+
223
+ // ─── Factory ──────────────────────────────────────────────────────────────────
224
+
225
+ const ERROR_MAP: Record<string, new (msg?: string) => RemitError> = {
226
+ INVALID_SIGNATURE: InvalidSignatureError,
227
+ NONCE_REUSED: NonceReusedError,
228
+ TIMESTAMP_EXPIRED: TimestampExpiredError,
229
+ UNAUTHORIZED: UnauthorizedError,
230
+ INSUFFICIENT_BALANCE: InsufficientBalanceError,
231
+ BELOW_MINIMUM: BelowMinimumError,
232
+ ESCROW_NOT_FOUND: EscrowNotFoundError,
233
+ ESCROW_ALREADY_FUNDED: EscrowAlreadyFundedError,
234
+ ESCROW_EXPIRED: EscrowExpiredError,
235
+ INVALID_INVOICE: InvalidInvoiceError,
236
+ DUPLICATE_INVOICE: DuplicateInvoiceError,
237
+ SELF_PAYMENT: SelfPaymentError,
238
+ INVALID_PAYMENT_TYPE: InvalidPaymentTypeError,
239
+ TAB_DEPLETED: TabDepletedError,
240
+ TAB_EXPIRED: TabExpiredError,
241
+ TAB_NOT_FOUND: TabNotFoundError,
242
+ STREAM_NOT_FOUND: StreamNotFoundError,
243
+ RATE_EXCEEDS_CAP: RateExceedsCapError,
244
+ BOUNTY_EXPIRED: BountyExpiredError,
245
+ BOUNTY_CLAIMED: BountyClaimedError,
246
+ BOUNTY_MAX_ATTEMPTS: BountyMaxAttemptsError,
247
+ BOUNTY_NOT_FOUND: BountyNotFoundError,
248
+ CHAIN_MISMATCH: ChainMismatchError,
249
+ CHAIN_UNSUPPORTED: ChainUnsupportedError,
250
+ RATE_LIMITED: RateLimitedError,
251
+ CANCEL_BLOCKED_CLAIM_START: CancelBlockedClaimStartError,
252
+ CANCEL_BLOCKED_EVIDENCE: CancelBlockedEvidenceError,
253
+ VERSION_MISMATCH: VersionMismatchError,
254
+ };
255
+
256
+ /** Map an API error code + message into the appropriate typed error. */
257
+ export function fromErrorCode(code: string, message?: string): RemitError {
258
+ const Cls = ERROR_MAP[code];
259
+ if (Cls) return new Cls(message);
260
+ return new RemitError(message ?? `Unknown error: ${code}`, code);
261
+ }
package/src/http.ts ADDED
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Authenticated HTTP client.
3
+ * - Signs every request with EIP-712 (method, path, timestamp, nonce)
4
+ * - Auto-retry with exponential backoff on 429 and 5xx (up to 3 retries)
5
+ * - Adds idempotency key header on all POST/PATCH/PUT requests
6
+ * - Maps API error codes to typed RemitError subclasses
7
+ */
8
+
9
+ import { randomBytes } from "node:crypto";
10
+ import { fromErrorCode, NetworkError, RateLimitedError } from "./errors.js";
11
+ import type { Signer } from "./signer.js";
12
+
13
+ // EIP-712 typed struct definition — must match server's auth.rs exactly.
14
+ // Struct name: APIRequest (not Request)
15
+ // Timestamp type: uint256 (not uint64 or uint32)
16
+ // Nonce type: bytes32 (not string)
17
+ const EIP712_TYPES = {
18
+ APIRequest: [
19
+ { name: "method", type: "string" },
20
+ { name: "path", type: "string" },
21
+ { name: "timestamp", type: "uint256" },
22
+ { name: "nonce", type: "bytes32" },
23
+ ],
24
+ } as const;
25
+
26
+ interface ApiErrorBody {
27
+ error?: { code?: string; message?: string };
28
+ code?: string;
29
+ message?: string;
30
+ }
31
+
32
+ /** 32-byte hex nonce with 0x prefix — required for bytes32 EIP-712 field. */
33
+ function newNonce(): `0x${string}` {
34
+ return `0x${randomBytes(32).toString("hex")}`;
35
+ }
36
+
37
+ function newIdempotencyKey(): string {
38
+ return randomBytes(16).toString("hex");
39
+ }
40
+
41
+ /** Convert a snake_case key to camelCase (e.g. "tx_hash" → "txHash"). */
42
+ function toCamel(key: string): string {
43
+ return key.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());
44
+ }
45
+
46
+ /** Recursively transform all object keys from snake_case to camelCase.
47
+ * The server (Rust/serde) emits snake_case; TypeScript SDK uses camelCase. */
48
+ function camelizeKeys(value: unknown): unknown {
49
+ if (Array.isArray(value)) return value.map(camelizeKeys);
50
+ if (value !== null && typeof value === "object") {
51
+ return Object.fromEntries(
52
+ Object.entries(value).map(([k, v]) => [toCamel(k), camelizeKeys(v)]),
53
+ );
54
+ }
55
+ return value;
56
+ }
57
+
58
+ const RETRYABLE = new Set([429, 500, 502, 503, 504]);
59
+ const DELAY_MS = [200, 600, 1800]; // exponential-ish
60
+
61
+ async function sleep(ms: number): Promise<void> {
62
+ return new Promise((resolve) => setTimeout(resolve, ms));
63
+ }
64
+
65
+ export interface HttpClientOptions {
66
+ signer: Signer;
67
+ baseUrl: string;
68
+ chainId: number;
69
+ verifyingContract?: string;
70
+ }
71
+
72
+ export class AuthenticatedClient {
73
+ readonly #signer: Signer;
74
+ readonly #baseUrl: string;
75
+ readonly #chainId: number;
76
+ readonly #verifyingContract: string;
77
+ /** Path prefix extracted from baseUrl (e.g. "/api/v0") to prepend when signing.
78
+ * The server verifies the full path (/api/v0/payments/direct) via OriginalUri,
79
+ * not just the relative segment (/payments/direct). */
80
+ readonly #signPathPrefix: string;
81
+
82
+ constructor({ signer, baseUrl, chainId, verifyingContract = "" }: HttpClientOptions) {
83
+ this.#signer = signer;
84
+ this.#baseUrl = baseUrl.replace(/\/$/, "");
85
+ this.#chainId = chainId;
86
+ this.#verifyingContract = verifyingContract;
87
+ // Parse path prefix from baseUrl so signed path matches OriginalUri on server.
88
+ const parsedUrl = new URL(this.#baseUrl);
89
+ this.#signPathPrefix = parsedUrl.pathname.replace(/\/$/, "");
90
+ }
91
+
92
+ async get<T>(path: string): Promise<T> {
93
+ return this.#request<T>("GET", path);
94
+ }
95
+
96
+ async post<T>(path: string, body?: unknown): Promise<T> {
97
+ return this.#request<T>("POST", path, body);
98
+ }
99
+
100
+ async put<T>(path: string, body?: unknown): Promise<T> {
101
+ return this.#request<T>("PUT", path, body);
102
+ }
103
+
104
+ async delete<T>(path: string): Promise<T> {
105
+ return this.#request<T>("DELETE", path);
106
+ }
107
+
108
+ async #request<T>(method: string, path: string, body?: unknown, attempt = 0): Promise<T> {
109
+ const timestamp = Math.floor(Date.now() / 1000);
110
+ const nonce = newNonce();
111
+
112
+ const domain = {
113
+ name: "remit.md",
114
+ version: "0.1",
115
+ chainId: this.#chainId,
116
+ verifyingContract: this.#verifyingContract,
117
+ };
118
+
119
+ // Sign the full path (prefix + relative path only, no query string) so it matches
120
+ // the path OriginalUri on the server. The server verifies only the path component.
121
+ // e.g. baseUrl "http://…/api/v0" + path "/events?wallet=0x…" → signed "/api/v0/events"
122
+ const pathOnly = path.split("?")[0];
123
+ const signedPath = `${this.#signPathPrefix}${pathOnly}`;
124
+
125
+ // Sign the request metadata (never body — body may be large)
126
+ const signature = await this.#signer.signTypedData(
127
+ domain,
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
+ EIP712_TYPES as any,
130
+ { method, path: signedPath, timestamp: BigInt(timestamp), nonce },
131
+ );
132
+
133
+ const headers: Record<string, string> = {
134
+ "Content-Type": "application/json",
135
+ "X-Remit-Agent": this.#signer.getAddress(),
136
+ "X-Remit-Signature": signature,
137
+ "X-Remit-Timestamp": String(timestamp),
138
+ "X-Remit-Nonce": nonce,
139
+ };
140
+
141
+ if (method === "POST" || method === "PUT" || method === "PATCH") {
142
+ headers["X-Idempotency-Key"] = newIdempotencyKey();
143
+ }
144
+
145
+ let response: Response;
146
+ try {
147
+ response = await fetch(`${this.#baseUrl}${path}`, {
148
+ method,
149
+ headers,
150
+ body: body !== undefined ? JSON.stringify(body) : undefined,
151
+ });
152
+ } catch (err) {
153
+ throw new NetworkError(`Network request failed: ${String(err)}`);
154
+ }
155
+
156
+ if (response.ok) {
157
+ if (response.status === 204) return undefined as T;
158
+ return camelizeKeys(await response.json()) as T;
159
+ }
160
+
161
+ // Retryable errors
162
+ if (RETRYABLE.has(response.status) && attempt < DELAY_MS.length) {
163
+ if (response.status === 429) {
164
+ // Respect Retry-After header if present
165
+ const retryAfter = response.headers.get("Retry-After");
166
+ const delay = retryAfter ? parseInt(retryAfter) * 1000 : DELAY_MS[attempt];
167
+ await sleep(delay);
168
+ } else {
169
+ await sleep(DELAY_MS[attempt]);
170
+ }
171
+ return this.#request<T>(method, path, body, attempt + 1);
172
+ }
173
+
174
+ // Parse error body
175
+ let errorBody: ApiErrorBody = {};
176
+ try {
177
+ errorBody = (await response.json()) as ApiErrorBody;
178
+ } catch {
179
+ // ignore parse failures
180
+ }
181
+
182
+ const code =
183
+ errorBody.error?.code ?? errorBody.code ?? `HTTP_${response.status}`;
184
+ const message =
185
+ errorBody.error?.message ?? errorBody.message ?? response.statusText;
186
+
187
+ if (response.status === 429) throw new RateLimitedError(message);
188
+ throw fromErrorCode(code, message);
189
+ }
190
+ }
package/src/index.ts ADDED
@@ -0,0 +1,94 @@
1
+ // A2A / AP2
2
+ export {
3
+ discoverAgent,
4
+ A2AClient,
5
+ getTaskTxHash,
6
+ } from "./a2a.js";
7
+ export type {
8
+ AgentCard,
9
+ A2ACapabilities,
10
+ A2AExtension,
11
+ A2ASkill,
12
+ A2AX402,
13
+ A2AFees,
14
+ A2ATask,
15
+ A2ATaskStatus,
16
+ A2AArtifact,
17
+ A2AArtifactPart,
18
+ IntentMandate,
19
+ SendOptions,
20
+ A2AClientOptions,
21
+ } from "./a2a.js";
22
+
23
+ // Core classes
24
+ export { RemitClient } from "./client.js";
25
+ export { Wallet } from "./wallet.js";
26
+ export type {
27
+ WalletOptions,
28
+ OpenTabOptions,
29
+ CloseTabOptions,
30
+ ChargeTabOptions,
31
+ OpenStreamOptions,
32
+ PostBountyOptions,
33
+ PlaceDepositOptions,
34
+ PermitSignature,
35
+ SignPermitOptions,
36
+ } from "./wallet.js";
37
+
38
+ // Signers
39
+ export { PrivateKeySigner } from "./signer.js";
40
+ export type { Signer, TypedDataDomain, TypedDataTypes } from "./signer.js";
41
+
42
+ // Errors
43
+ export {
44
+ RemitError,
45
+ fromErrorCode,
46
+ InvalidSignatureError,
47
+ NonceReusedError,
48
+ TimestampExpiredError,
49
+ UnauthorizedError,
50
+ InsufficientBalanceError,
51
+ BelowMinimumError,
52
+ EscrowNotFoundError,
53
+ EscrowAlreadyFundedError,
54
+ EscrowExpiredError,
55
+ InvalidInvoiceError,
56
+ DuplicateInvoiceError,
57
+ SelfPaymentError,
58
+ InvalidPaymentTypeError,
59
+ TabDepletedError,
60
+ TabExpiredError,
61
+ TabNotFoundError,
62
+ StreamNotFoundError,
63
+ RateExceedsCapError,
64
+ BountyExpiredError,
65
+ BountyClaimedError,
66
+ BountyMaxAttemptsError,
67
+ BountyNotFoundError,
68
+ ChainMismatchError,
69
+ ChainUnsupportedError,
70
+ RateLimitedError,
71
+ CancelBlockedClaimStartError,
72
+ CancelBlockedEvidenceError,
73
+ VersionMismatchError,
74
+ NetworkError,
75
+ } from "./errors.js";
76
+
77
+ // Models
78
+ export * from "./models/index.js";
79
+
80
+ // Testing utilities
81
+ export { MockRemit, MockWallet } from "./testing/mock.js";
82
+ export { LocalChain } from "./testing/local.js";
83
+
84
+ // x402 client middleware
85
+ export { X402Client, AllowanceExceededError } from "./x402.js";
86
+ export type { X402ClientOptions } from "./x402.js";
87
+
88
+ // x402 provider middleware
89
+ export { X402Paywall } from "./provider.js";
90
+ export type { PaywallOptions, CheckResult } from "./provider.js";
91
+
92
+ // Integrations
93
+ export { remitTools } from "./integrations/vercel-ai.js";
94
+ export type { RemitToolDescriptor } from "./integrations/vercel-ai.js";