@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
@@ -0,0 +1,302 @@
1
+ import { describe, it, mock, beforeEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { PrivateKeySigner } from "../src/signer.js";
5
+ import { AllowanceExceededError, X402Client } from "../src/x402.js";
6
+
7
+ // Anvil account #0 — well-known test key.
8
+ const TEST_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
9
+ const TEST_ADDR = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
10
+ const PROVIDER = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
11
+ const USDC = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
12
+
13
+ function makePaymentRequired(opts: {
14
+ amount?: string;
15
+ scheme?: string;
16
+ network?: string;
17
+ payTo?: string;
18
+ maxTimeoutSeconds?: number;
19
+ resource?: string;
20
+ description?: string;
21
+ mimeType?: string;
22
+ }): string {
23
+ const payload: Record<string, unknown> = {
24
+ scheme: opts.scheme ?? "exact",
25
+ network: opts.network ?? "eip155:31337",
26
+ amount: opts.amount ?? "100000",
27
+ asset: USDC,
28
+ payTo: opts.payTo ?? PROVIDER,
29
+ maxTimeoutSeconds: opts.maxTimeoutSeconds ?? 30,
30
+ };
31
+ if (opts.resource !== undefined) payload["resource"] = opts.resource;
32
+ if (opts.description !== undefined) payload["description"] = opts.description;
33
+ if (opts.mimeType !== undefined) payload["mimeType"] = opts.mimeType;
34
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
35
+ }
36
+
37
+ function makeMockFetch(responses: Array<{ status: number; headers?: Record<string, string> }>) {
38
+ let callIndex = 0;
39
+ const capturedCalls: Array<{ url: string; init?: RequestInit }> = [];
40
+
41
+ const fetchFn = async (url: string | URL | Request, init?: RequestInit): Promise<Response> => {
42
+ const r = responses[callIndex++];
43
+ if (!r) throw new Error("Unexpected fetch call");
44
+ capturedCalls.push({ url: String(url), init });
45
+ const headerMap = new Headers(r.headers ?? {});
46
+ return new Response(null, { status: r.status, headers: headerMap });
47
+ };
48
+
49
+ return { fetchFn, capturedCalls };
50
+ }
51
+
52
+ // ─── Construction ─────────────────────────────────────────────────────────────
53
+
54
+ describe("X402Client construction", () => {
55
+ it("constructs with signer and address", () => {
56
+ const signer = new PrivateKeySigner(TEST_KEY);
57
+ const client = new X402Client({ signer, address: TEST_ADDR });
58
+ // Should not throw; toJSON() reveals address only.
59
+ assert.equal(client.toJSON().address, TEST_ADDR);
60
+ });
61
+
62
+ it("defaults maxAutoPayUsdc to 0.10", () => {
63
+ const signer = new PrivateKeySigner(TEST_KEY);
64
+ const client = new X402Client({ signer, address: TEST_ADDR });
65
+ // Only way to observe is via toJSON (no limit getter).
66
+ assert.ok(client instanceof X402Client);
67
+ });
68
+ });
69
+
70
+ // ─── AllowanceExceededError ────────────────────────────────────────────────────
71
+
72
+ describe("AllowanceExceededError", () => {
73
+ it("includes amounts in message", () => {
74
+ const err = new AllowanceExceededError(0.5, 0.1);
75
+ assert.ok(err.message.includes("0.500000"));
76
+ assert.ok(err.message.includes("0.100000"));
77
+ assert.equal(err.amountUsdc, 0.5);
78
+ assert.equal(err.limitUsdc, 0.1);
79
+ assert.equal(err.name, "AllowanceExceededError");
80
+ });
81
+
82
+ it("is an instance of Error", () => {
83
+ const err = new AllowanceExceededError(1.0, 0.5);
84
+ assert.ok(err instanceof Error);
85
+ });
86
+ });
87
+
88
+ // ─── fetch passthrough ─────────────────────────────────────────────────────────
89
+
90
+ describe("X402Client.fetch — non-402 passthrough", () => {
91
+ it("returns 200 response unchanged", async () => {
92
+ const signer = new PrivateKeySigner(TEST_KEY);
93
+ const { fetchFn } = makeMockFetch([{ status: 200 }]);
94
+ const client = new X402Client({ signer, address: TEST_ADDR });
95
+
96
+ // Temporarily patch globalThis.fetch.
97
+ const orig = globalThis.fetch;
98
+ try {
99
+ (globalThis as Record<string, unknown>)["fetch"] = fetchFn;
100
+ const resp = await client.fetch("http://example.com/data");
101
+ assert.equal(resp.status, 200);
102
+ } finally {
103
+ globalThis.fetch = orig;
104
+ }
105
+ });
106
+ });
107
+
108
+ // ─── 402 handling ─────────────────────────────────────────────────────────────
109
+
110
+ describe("X402Client.fetch — 402 handling", () => {
111
+ it("throws when PAYMENT-REQUIRED header is absent", async () => {
112
+ const signer = new PrivateKeySigner(TEST_KEY);
113
+ const { fetchFn } = makeMockFetch([{ status: 402 }]);
114
+ const client = new X402Client({ signer, address: TEST_ADDR, maxAutoPayUsdc: 1.0 });
115
+
116
+ const orig = globalThis.fetch;
117
+ try {
118
+ (globalThis as Record<string, unknown>)["fetch"] = fetchFn;
119
+ await assert.rejects(
120
+ () => client.fetch("http://example.com/data"),
121
+ /PAYMENT-REQUIRED/,
122
+ );
123
+ } finally {
124
+ globalThis.fetch = orig;
125
+ }
126
+ });
127
+
128
+ it("throws for unsupported scheme", async () => {
129
+ const signer = new PrivateKeySigner(TEST_KEY);
130
+ const header = makePaymentRequired({ scheme: "upto" });
131
+ const { fetchFn } = makeMockFetch([{ status: 402, headers: { "payment-required": header } }]);
132
+ const client = new X402Client({ signer, address: TEST_ADDR, maxAutoPayUsdc: 1.0 });
133
+
134
+ const orig = globalThis.fetch;
135
+ try {
136
+ (globalThis as Record<string, unknown>)["fetch"] = fetchFn;
137
+ await assert.rejects(
138
+ () => client.fetch("http://example.com/data"),
139
+ /scheme/,
140
+ );
141
+ } finally {
142
+ globalThis.fetch = orig;
143
+ }
144
+ });
145
+
146
+ it("throws AllowanceExceededError when payment exceeds limit", async () => {
147
+ const signer = new PrivateKeySigner(TEST_KEY);
148
+ // 100000 base units = 0.10 USDC; limit = 0.05
149
+ const header = makePaymentRequired({ amount: "100000" });
150
+ const { fetchFn } = makeMockFetch([{ status: 402, headers: { "payment-required": header } }]);
151
+ const client = new X402Client({ signer, address: TEST_ADDR, maxAutoPayUsdc: 0.05 });
152
+
153
+ const orig = globalThis.fetch;
154
+ try {
155
+ (globalThis as Record<string, unknown>)["fetch"] = fetchFn;
156
+ await assert.rejects(
157
+ () => client.fetch("http://example.com/data"),
158
+ AllowanceExceededError,
159
+ );
160
+ } finally {
161
+ globalThis.fetch = orig;
162
+ }
163
+ });
164
+
165
+ it("retries with PAYMENT-SIGNATURE header and returns 200", async () => {
166
+ const signer = new PrivateKeySigner(TEST_KEY);
167
+ const header402 = makePaymentRequired({ amount: "100000" });
168
+ const { fetchFn, capturedCalls } = makeMockFetch([
169
+ { status: 402, headers: { "payment-required": header402 } },
170
+ { status: 200 },
171
+ ]);
172
+ const client = new X402Client({ signer, address: TEST_ADDR, maxAutoPayUsdc: 1.0 });
173
+
174
+ const orig = globalThis.fetch;
175
+ try {
176
+ (globalThis as Record<string, unknown>)["fetch"] = fetchFn;
177
+ const resp = await client.fetch("http://example.com/data");
178
+ assert.equal(resp.status, 200);
179
+ } finally {
180
+ globalThis.fetch = orig;
181
+ }
182
+
183
+ // Two fetch calls: initial + retry.
184
+ assert.equal(capturedCalls.length, 2);
185
+
186
+ // Retry must carry PAYMENT-SIGNATURE header.
187
+ const retryInit = capturedCalls[1]!.init!;
188
+ const retryHeaders = new Headers(retryInit.headers);
189
+ const paymentSig = retryHeaders.get("payment-signature");
190
+ assert.ok(paymentSig, "PAYMENT-SIGNATURE header must be present on retry");
191
+
192
+ // Decode and validate payload structure.
193
+ const payload = JSON.parse(Buffer.from(paymentSig!, "base64").toString("utf8")) as {
194
+ scheme: string;
195
+ network: string;
196
+ x402Version: number;
197
+ payload: {
198
+ signature: string;
199
+ authorization: {
200
+ from: string;
201
+ to: string;
202
+ value: string;
203
+ validAfter: string;
204
+ validBefore: string;
205
+ nonce: string;
206
+ };
207
+ };
208
+ };
209
+
210
+ assert.equal(payload.scheme, "exact");
211
+ assert.equal(payload.network, "eip155:31337");
212
+ assert.equal(payload.x402Version, 1);
213
+
214
+ const auth = payload.payload.authorization;
215
+ assert.equal(auth.from.toLowerCase(), TEST_ADDR.toLowerCase());
216
+ assert.equal(auth.to.toLowerCase(), PROVIDER.toLowerCase());
217
+ assert.equal(auth.value, "100000");
218
+ assert.equal(auth.validAfter, "0");
219
+ assert.ok(auth.nonce.startsWith("0x"));
220
+ assert.equal(auth.nonce.length, 66); // 0x + 64 hex chars = 32 bytes
221
+
222
+ // Signature must be 0x-prefixed hex.
223
+ assert.ok(payload.payload.signature.startsWith("0x"));
224
+ });
225
+
226
+ it("preserves other request headers on retry", async () => {
227
+ const signer = new PrivateKeySigner(TEST_KEY);
228
+ const header402 = makePaymentRequired({ amount: "1000" });
229
+ const { fetchFn, capturedCalls } = makeMockFetch([
230
+ { status: 402, headers: { "payment-required": header402 } },
231
+ { status: 200 },
232
+ ]);
233
+ const client = new X402Client({ signer, address: TEST_ADDR, maxAutoPayUsdc: 1.0 });
234
+
235
+ const orig = globalThis.fetch;
236
+ try {
237
+ (globalThis as Record<string, unknown>)["fetch"] = fetchFn;
238
+ await client.fetch("http://example.com/data", {
239
+ headers: { Authorization: "Bearer abc123", "X-Custom": "hello" },
240
+ });
241
+ } finally {
242
+ globalThis.fetch = orig;
243
+ }
244
+
245
+ const retryHeaders = new Headers(capturedCalls[1]!.init!.headers);
246
+ assert.equal(retryHeaders.get("authorization"), "Bearer abc123");
247
+ assert.equal(retryHeaders.get("x-custom"), "hello");
248
+ assert.ok(retryHeaders.get("payment-signature"));
249
+ });
250
+
251
+ it("exposes V2 resource/description/mimeType via lastPayment", async () => {
252
+ const signer = new PrivateKeySigner(TEST_KEY);
253
+ const header402 = makePaymentRequired({
254
+ amount: "1000",
255
+ resource: "/api/v0/premium",
256
+ description: "Access to premium data",
257
+ mimeType: "application/json",
258
+ });
259
+ const { fetchFn } = makeMockFetch([
260
+ { status: 402, headers: { "payment-required": header402 } },
261
+ { status: 200 },
262
+ ]);
263
+ const client = new X402Client({ signer, address: TEST_ADDR, maxAutoPayUsdc: 1.0 });
264
+
265
+ const orig = globalThis.fetch;
266
+ try {
267
+ (globalThis as Record<string, unknown>)["fetch"] = fetchFn;
268
+ await client.fetch("http://example.com/api/v0/premium");
269
+ } finally {
270
+ globalThis.fetch = orig;
271
+ }
272
+
273
+ assert.ok(client.lastPayment, "lastPayment must be set after payment");
274
+ assert.equal(client.lastPayment!.resource, "/api/v0/premium");
275
+ assert.equal(client.lastPayment!.description, "Access to premium data");
276
+ assert.equal(client.lastPayment!.mimeType, "application/json");
277
+ });
278
+
279
+ it("parses chainId correctly from CAIP-2 network string", async () => {
280
+ const signer = new PrivateKeySigner(TEST_KEY);
281
+ const header402 = makePaymentRequired({ amount: "1000", network: "eip155:84532" });
282
+ const { fetchFn, capturedCalls } = makeMockFetch([
283
+ { status: 402, headers: { "payment-required": header402 } },
284
+ { status: 200 },
285
+ ]);
286
+ const client = new X402Client({ signer, address: TEST_ADDR, maxAutoPayUsdc: 1.0 });
287
+
288
+ const orig = globalThis.fetch;
289
+ try {
290
+ (globalThis as Record<string, unknown>)["fetch"] = fetchFn;
291
+ await client.fetch("http://example.com/resource");
292
+ } finally {
293
+ globalThis.fetch = orig;
294
+ }
295
+
296
+ const retryHeaders = new Headers(capturedCalls[1]!.init!.headers);
297
+ const payload = JSON.parse(
298
+ Buffer.from(retryHeaders.get("payment-signature")!, "base64").toString("utf8"),
299
+ ) as { network: string };
300
+ assert.equal(payload.network, "eip155:84532");
301
+ });
302
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist", "tests"]
19
+ }