@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,304 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { X402Paywall } from "../src/provider.js";
5
+
6
+ const WALLET = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
7
+ const USDC = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
8
+ const NETWORK = "eip155:31337";
9
+
10
+ function makePaywall(overrides: Partial<ConstructorParameters<typeof X402Paywall>[0]> = {}): X402Paywall {
11
+ return new X402Paywall({
12
+ walletAddress: WALLET,
13
+ amountUsdc: 0.001,
14
+ network: NETWORK,
15
+ asset: USDC,
16
+ facilitatorUrl: "http://localhost:3000",
17
+ facilitatorToken: "test-token",
18
+ ...overrides,
19
+ });
20
+ }
21
+
22
+ function encodeSig(payload: unknown): string {
23
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
24
+ }
25
+
26
+ function makeDummyPaymentSig(): string {
27
+ return encodeSig({
28
+ scheme: "exact",
29
+ network: NETWORK,
30
+ x402Version: 1,
31
+ payload: {
32
+ signature: "0xdeadbeef",
33
+ authorization: {
34
+ from: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
35
+ to: WALLET,
36
+ value: "1000",
37
+ validAfter: "0",
38
+ validBefore: "9999999999",
39
+ nonce: "0xabcd1234",
40
+ },
41
+ },
42
+ });
43
+ }
44
+
45
+ function mockFetch(response: { ok: boolean; json: () => unknown }): typeof globalThis.fetch {
46
+ return async () =>
47
+ ({
48
+ ok: response.ok,
49
+ json: async () => response.json(),
50
+ }) as Response;
51
+ }
52
+
53
+ // ─── Construction ─────────────────────────────────────────────────────────────
54
+
55
+ describe("X402Paywall construction", () => {
56
+ it("toJSON() reveals only non-sensitive fields", () => {
57
+ const pw = makePaywall();
58
+ const j = pw.toJSON();
59
+ assert.equal(j.walletAddress, WALLET);
60
+ assert.equal(j.network, NETWORK);
61
+ // token must not appear
62
+ assert.ok(!("facilitatorToken" in j));
63
+ });
64
+ });
65
+
66
+ // ─── paymentRequiredHeader ────────────────────────────────────────────────────
67
+
68
+ describe("X402Paywall.paymentRequiredHeader", () => {
69
+ it("produces correctly structured base64 JSON", () => {
70
+ const pw = makePaywall({ amountUsdc: 0.005 });
71
+ const raw = pw.paymentRequiredHeader();
72
+ const payload = JSON.parse(Buffer.from(raw, "base64").toString("utf8")) as {
73
+ scheme: string;
74
+ network: string;
75
+ amount: string;
76
+ asset: string;
77
+ payTo: string;
78
+ maxTimeoutSeconds: number;
79
+ };
80
+
81
+ assert.equal(payload.scheme, "exact");
82
+ assert.equal(payload.network, NETWORK);
83
+ assert.equal(payload.amount, "5000"); // 0.005 USDC * 1_000_000
84
+ assert.equal(payload.asset, USDC);
85
+ assert.equal(payload.payTo, WALLET);
86
+ assert.ok(typeof payload.maxTimeoutSeconds === "number");
87
+ });
88
+
89
+ it("converts 0.001 USDC to 1000 base units", () => {
90
+ const pw = makePaywall({ amountUsdc: 0.001 });
91
+ const raw = pw.paymentRequiredHeader();
92
+ const payload = JSON.parse(Buffer.from(raw, "base64").toString("utf8")) as { amount: string };
93
+ assert.equal(payload.amount, "1000");
94
+ });
95
+
96
+ it("includes V2 resource/description/mimeType when configured", () => {
97
+ const pw = makePaywall({
98
+ resource: "/v1/data",
99
+ description: "Market data feed",
100
+ mimeType: "application/json",
101
+ });
102
+ const raw = pw.paymentRequiredHeader();
103
+ const payload = JSON.parse(Buffer.from(raw, "base64").toString("utf8")) as Record<string, unknown>;
104
+ assert.equal(payload["resource"], "/v1/data");
105
+ assert.equal(payload["description"], "Market data feed");
106
+ assert.equal(payload["mimeType"], "application/json");
107
+ });
108
+
109
+ it("omits V2 fields when not configured", () => {
110
+ const pw = makePaywall();
111
+ const raw = pw.paymentRequiredHeader();
112
+ const payload = JSON.parse(Buffer.from(raw, "base64").toString("utf8")) as Record<string, unknown>;
113
+ assert.ok(!("resource" in payload), "resource must be absent");
114
+ assert.ok(!("description" in payload), "description must be absent");
115
+ assert.ok(!("mimeType" in payload), "mimeType must be absent");
116
+ });
117
+ });
118
+
119
+ // ─── check ─────────────────────────────────────────────────────────────────────
120
+
121
+ describe("X402Paywall.check", () => {
122
+ it("returns { isValid: false } when payment_sig is null", async () => {
123
+ const pw = makePaywall();
124
+ const result = await pw.check(null);
125
+ assert.equal(result.isValid, false);
126
+ assert.equal(result.invalidReason, undefined);
127
+ });
128
+
129
+ it("returns INVALID_PAYLOAD for malformed base64", async () => {
130
+ const pw = makePaywall();
131
+ const result = await pw.check("!!!not-valid-base64!!!");
132
+ assert.equal(result.isValid, false);
133
+ assert.equal(result.invalidReason, "INVALID_PAYLOAD");
134
+ });
135
+
136
+ it("returns { isValid: true } when facilitator says valid", async () => {
137
+ const pw = makePaywall();
138
+ const orig = globalThis.fetch;
139
+ try {
140
+ (globalThis as Record<string, unknown>)["fetch"] = mockFetch({ ok: true, json: () => ({ isValid: true }) });
141
+ const result = await pw.check(makeDummyPaymentSig());
142
+ assert.equal(result.isValid, true);
143
+ } finally {
144
+ globalThis.fetch = orig;
145
+ }
146
+ });
147
+
148
+ it("returns invalidReason when facilitator says invalid", async () => {
149
+ const pw = makePaywall();
150
+ const orig = globalThis.fetch;
151
+ try {
152
+ (globalThis as Record<string, unknown>)["fetch"] = mockFetch({
153
+ ok: true,
154
+ json: () => ({ isValid: false, invalidReason: "SIGNATURE_INVALID" }),
155
+ });
156
+ const result = await pw.check(makeDummyPaymentSig());
157
+ assert.equal(result.isValid, false);
158
+ assert.equal(result.invalidReason, "SIGNATURE_INVALID");
159
+ } finally {
160
+ globalThis.fetch = orig;
161
+ }
162
+ });
163
+
164
+ it("returns FACILITATOR_ERROR when fetch throws", async () => {
165
+ const pw = makePaywall();
166
+ const orig = globalThis.fetch;
167
+ try {
168
+ (globalThis as Record<string, unknown>)["fetch"] = async () => { throw new Error("network error"); };
169
+ const result = await pw.check(makeDummyPaymentSig());
170
+ assert.equal(result.isValid, false);
171
+ assert.equal(result.invalidReason, "FACILITATOR_ERROR");
172
+ } finally {
173
+ globalThis.fetch = orig;
174
+ }
175
+ });
176
+
177
+ it("returns FACILITATOR_ERROR when facilitator returns non-ok status", async () => {
178
+ const pw = makePaywall();
179
+ const orig = globalThis.fetch;
180
+ try {
181
+ (globalThis as Record<string, unknown>)["fetch"] = mockFetch({ ok: false, json: () => ({}) });
182
+ const result = await pw.check(makeDummyPaymentSig());
183
+ assert.equal(result.isValid, false);
184
+ assert.equal(result.invalidReason, "FACILITATOR_ERROR");
185
+ } finally {
186
+ globalThis.fetch = orig;
187
+ }
188
+ });
189
+
190
+ it("sends Authorization header when token is configured", async () => {
191
+ const pw = makePaywall({ facilitatorToken: "my-jwt" });
192
+ let capturedHeaders: Record<string, string> = {};
193
+ const orig = globalThis.fetch;
194
+ try {
195
+ (globalThis as Record<string, unknown>)["fetch"] = async (_url: unknown, init?: RequestInit) => {
196
+ capturedHeaders = Object.fromEntries(new Headers(init?.headers).entries());
197
+ return { ok: true, json: async () => ({ isValid: true }) } as Response;
198
+ };
199
+ await pw.check(makeDummyPaymentSig());
200
+ } finally {
201
+ globalThis.fetch = orig;
202
+ }
203
+ assert.equal(capturedHeaders["authorization"], "Bearer my-jwt");
204
+ });
205
+
206
+ it("omits Authorization header when no token", async () => {
207
+ const pw = new X402Paywall({ walletAddress: WALLET, amountUsdc: 0.001, network: NETWORK, asset: USDC });
208
+ let capturedHeaders: Record<string, string> = {};
209
+ const orig = globalThis.fetch;
210
+ try {
211
+ (globalThis as Record<string, unknown>)["fetch"] = async (_url: unknown, init?: RequestInit) => {
212
+ capturedHeaders = Object.fromEntries(new Headers(init?.headers).entries());
213
+ return { ok: true, json: async () => ({ isValid: true }) } as Response;
214
+ };
215
+ await pw.check(makeDummyPaymentSig());
216
+ } finally {
217
+ globalThis.fetch = orig;
218
+ }
219
+ assert.ok(!("authorization" in capturedHeaders));
220
+ });
221
+ });
222
+
223
+ // ─── handle ────────────────────────────────────────────────────────────────────
224
+
225
+ describe("X402Paywall.handle", () => {
226
+ it("returns 402 Response when PAYMENT-SIGNATURE header is absent", async () => {
227
+ const pw = makePaywall();
228
+ const req = new Request("http://example.com/v1/data");
229
+ const orig = globalThis.fetch;
230
+ try {
231
+ // check() will return false (null sig) without hitting facilitator
232
+ const resp = await pw.handle(req);
233
+ assert.ok(resp instanceof Response);
234
+ assert.equal(resp!.status, 402);
235
+ assert.ok(resp!.headers.get("payment-required"));
236
+ } finally {
237
+ globalThis.fetch = orig;
238
+ }
239
+ });
240
+
241
+ it("returns null when payment is valid", async () => {
242
+ const pw = makePaywall();
243
+ const req = new Request("http://example.com/v1/data", {
244
+ headers: { "payment-signature": makeDummyPaymentSig() },
245
+ });
246
+ const orig = globalThis.fetch;
247
+ try {
248
+ (globalThis as Record<string, unknown>)["fetch"] = mockFetch({ ok: true, json: () => ({ isValid: true }) });
249
+ const resp = await pw.handle(req);
250
+ assert.equal(resp, null);
251
+ } finally {
252
+ globalThis.fetch = orig;
253
+ }
254
+ });
255
+ });
256
+
257
+ // ─── honoMiddleware ────────────────────────────────────────────────────────────
258
+
259
+ describe("X402Paywall.honoMiddleware", () => {
260
+ function makeHonoContext(paymentSig?: string): {
261
+ req: { raw: Request };
262
+ header(name: string, value: string): void;
263
+ body(content: string, status?: number): Response;
264
+ } {
265
+ const headers = new Headers();
266
+ if (paymentSig) headers.set("payment-signature", paymentSig);
267
+ const raw = new Request("http://example.com/v1/data", { headers });
268
+ const resHeaders: Record<string, string> = {};
269
+ return {
270
+ req: { raw },
271
+ header(name: string, value: string) { resHeaders[name] = value; },
272
+ body(content: string, status = 200): Response {
273
+ return new Response(content, { status, headers: resHeaders });
274
+ },
275
+ };
276
+ }
277
+
278
+ it("returns 402 Response when payment is absent", async () => {
279
+ const pw = makePaywall();
280
+ const middleware = pw.honoMiddleware();
281
+ const c = makeHonoContext();
282
+ let nextCalled = false;
283
+ const resp = await middleware(c, async () => { nextCalled = true; });
284
+ assert.ok(resp instanceof Response, "should return a Response");
285
+ assert.equal((resp as Response).status, 402);
286
+ assert.ok(!nextCalled, "next should not be called");
287
+ });
288
+
289
+ it("calls next when payment is valid", async () => {
290
+ const pw = makePaywall();
291
+ const middleware = pw.honoMiddleware();
292
+ const c = makeHonoContext(makeDummyPaymentSig());
293
+ let nextCalled = false;
294
+ const orig = globalThis.fetch;
295
+ try {
296
+ (globalThis as Record<string, unknown>)["fetch"] = mockFetch({ ok: true, json: () => ({ isValid: true }) });
297
+ const resp = await middleware(c, async () => { nextCalled = true; });
298
+ assert.equal(resp, undefined);
299
+ assert.ok(nextCalled, "next must be called when payment is valid");
300
+ } finally {
301
+ globalThis.fetch = orig;
302
+ }
303
+ });
304
+ });
@@ -0,0 +1,108 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { inspect } from "node:util";
4
+
5
+ import { Wallet } from "../src/wallet.js";
6
+ import { PrivateKeySigner } from "../src/signer.js";
7
+
8
+ const TEST_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
9
+ const TEST_ADDR = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
10
+
11
+ describe("Wallet construction", () => {
12
+ it("constructs from private key", () => {
13
+ const wallet = new Wallet({ privateKey: TEST_KEY });
14
+ assert.equal(wallet.address.toLowerCase(), TEST_ADDR.toLowerCase());
15
+ });
16
+
17
+ it("constructs from custom Signer", () => {
18
+ const signer = new PrivateKeySigner(TEST_KEY);
19
+ const wallet = new Wallet({ signer });
20
+ assert.equal(wallet.address.toLowerCase(), TEST_ADDR.toLowerCase());
21
+ });
22
+
23
+ it("throws without key or signer when REMITMD_KEY not set", () => {
24
+ const saved = process.env["REMITMD_KEY"];
25
+ delete process.env["REMITMD_KEY"];
26
+ try {
27
+ assert.throws(() => new Wallet({}), /REMITMD_KEY/);
28
+ } finally {
29
+ if (saved !== undefined) process.env["REMITMD_KEY"] = saved;
30
+ }
31
+ });
32
+
33
+ it("Wallet.create() generates unique addresses", () => {
34
+ const a = Wallet.create();
35
+ const b = Wallet.create();
36
+ assert.notEqual(a.address, b.address);
37
+ });
38
+
39
+ it("Wallet.fromEnv() throws when REMITMD_KEY not set", () => {
40
+ const saved = process.env["REMITMD_KEY"];
41
+ delete process.env["REMITMD_KEY"];
42
+ try {
43
+ assert.throws(() => Wallet.fromEnv(), /REMITMD_KEY/);
44
+ } finally {
45
+ if (saved) process.env["REMITMD_KEY"] = saved;
46
+ }
47
+ });
48
+
49
+ it("Wallet.fromEnv() succeeds when REMITMD_KEY is set", () => {
50
+ process.env["REMITMD_KEY"] = TEST_KEY;
51
+ try {
52
+ const wallet = Wallet.fromEnv();
53
+ assert.equal(wallet.address.toLowerCase(), TEST_ADDR.toLowerCase());
54
+ } finally {
55
+ delete process.env["REMITMD_KEY"];
56
+ }
57
+ });
58
+ });
59
+
60
+ describe("Wallet security", () => {
61
+ it("toJSON does not expose private key", () => {
62
+ const wallet = new Wallet({ privateKey: TEST_KEY });
63
+ const json = JSON.stringify(wallet);
64
+ assert(!json.includes(TEST_KEY), "private key must not appear in JSON");
65
+ assert(!json.includes("ac0974"), "private key fragment must not appear in JSON");
66
+ });
67
+
68
+ it("inspect does not expose private key", () => {
69
+ const wallet = new Wallet({ privateKey: TEST_KEY });
70
+ const repr = inspect(wallet);
71
+ assert(!repr.includes("ac0974"), "private key must not appear in inspect output");
72
+ });
73
+
74
+ it("PrivateKeySigner toJSON does not expose key", () => {
75
+ const signer = new PrivateKeySigner(TEST_KEY);
76
+ const json = JSON.stringify(signer);
77
+ assert(!json.includes("ac0974"), "private key must not appear in signer JSON");
78
+ assert(json.includes(TEST_ADDR.toLowerCase()) || json.includes("0xf39"), "address should be present");
79
+ });
80
+ });
81
+
82
+ describe("PrivateKeySigner", () => {
83
+ it("accepts key with 0x prefix", () => {
84
+ const signer = new PrivateKeySigner(TEST_KEY);
85
+ assert.equal(signer.getAddress().toLowerCase(), TEST_ADDR.toLowerCase());
86
+ });
87
+
88
+ it("accepts key without 0x prefix", () => {
89
+ const signer = new PrivateKeySigner(TEST_KEY.slice(2));
90
+ assert.equal(signer.getAddress().toLowerCase(), TEST_ADDR.toLowerCase());
91
+ });
92
+
93
+ it("fromHex factory works", () => {
94
+ const signer = PrivateKeySigner.fromHex(TEST_KEY);
95
+ assert.equal(signer.getAddress().toLowerCase(), TEST_ADDR.toLowerCase());
96
+ });
97
+
98
+ it("signs typed data and returns hex string", async () => {
99
+ const signer = new PrivateKeySigner(TEST_KEY);
100
+ const sig = await signer.signTypedData(
101
+ { name: "Test", version: "1" },
102
+ { Request: [{ name: "value", type: "string" }] },
103
+ { value: "hello" },
104
+ );
105
+ assert(sig.startsWith("0x"), "signature should be hex");
106
+ assert(sig.length >= 130, "signature should be at least 65 bytes");
107
+ });
108
+ });