@pay-skill/sdk 0.1.1 → 0.1.3
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/README.md +154 -154
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -2
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +8 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +106 -54
- package/dist/client.js.map +1 -1
- package/dist/eip3009.d.ts +24 -0
- package/dist/eip3009.d.ts.map +1 -0
- package/dist/eip3009.js +56 -0
- package/dist/eip3009.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +35 -26
- package/dist/models.d.ts.map +1 -1
- package/dist/signer.d.ts +1 -1
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +1 -2
- package/dist/signer.js.map +1 -1
- package/dist/wallet.d.ts.map +1 -1
- package/dist/wallet.js +6 -3
- package/dist/wallet.js.map +1 -1
- package/jsr.json +12 -0
- package/package.json +48 -44
- package/src/auth.ts +200 -200
- package/src/client.ts +644 -644
- package/src/eip3009.ts +79 -79
- package/src/index.ts +51 -51
- package/src/models.ts +77 -77
- package/src/ows-signer.ts +223 -223
- package/src/signer.ts +147 -147
- package/src/wallet.ts +445 -445
- package/tests/test_auth_rejection.ts +154 -154
- package/tests/test_crypto.ts +251 -251
- package/tests/test_e2e.ts +158 -158
- package/tests/test_ows_integration.ts +92 -92
- package/tests/test_ows_signer.ts +365 -365
- package/tests/test_validation.ts +66 -66
package/tests/test_crypto.ts
CHANGED
|
@@ -1,251 +1,251 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Crypto round-trip tests — proves that:
|
|
3
|
-
* 1. Address derivation uses real secp256k1 (not FNV hash)
|
|
4
|
-
* 2. EIP-712 signing produces valid signatures that the server can recover
|
|
5
|
-
* 3. buildAuthHeaders produces valid auth headers
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it } from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { privateKeyToAccount } from "viem/accounts";
|
|
11
|
-
import { recoverAddress, type Hex, type Address } from "viem";
|
|
12
|
-
|
|
13
|
-
import { PrivateKeySigner, Wallet } from "../src/wallet.js";
|
|
14
|
-
import { RawKeySigner } from "../src/signer.js";
|
|
15
|
-
import { buildAuthHeaders, computeEip712Hash } from "../src/auth.js";
|
|
16
|
-
|
|
17
|
-
// Anvil account #0 — well-known test key
|
|
18
|
-
const ANVIL_PK =
|
|
19
|
-
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" as Hex;
|
|
20
|
-
const ANVIL_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" as Address;
|
|
21
|
-
|
|
22
|
-
const TEST_ROUTER = "0x5FbDB2315678afecb367f032d93F642f64180aa3" as Address;
|
|
23
|
-
const TEST_CHAIN_ID = 8453;
|
|
24
|
-
|
|
25
|
-
describe("Address derivation", () => {
|
|
26
|
-
it("derives correct address from Anvil #0 private key via Wallet", () => {
|
|
27
|
-
const wallet = new Wallet({
|
|
28
|
-
privateKey: ANVIL_PK,
|
|
29
|
-
chain: "8453",
|
|
30
|
-
apiUrl: "http://localhost:3000/api/v1",
|
|
31
|
-
routerAddress: TEST_ROUTER,
|
|
32
|
-
});
|
|
33
|
-
assert.equal(wallet.address, ANVIL_ADDRESS);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("derives correct address via PrivateKeySigner", () => {
|
|
37
|
-
const signer = new PrivateKeySigner(ANVIL_PK);
|
|
38
|
-
assert.equal(signer.address, ANVIL_ADDRESS);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("derives correct address via RawKeySigner", () => {
|
|
42
|
-
const signer = new RawKeySigner(ANVIL_PK);
|
|
43
|
-
assert.equal(signer.address, ANVIL_ADDRESS);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("works without 0x prefix", () => {
|
|
47
|
-
const signer = new PrivateKeySigner(ANVIL_PK.slice(2));
|
|
48
|
-
assert.equal(signer.address, ANVIL_ADDRESS);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe("EIP-712 signing round-trip", () => {
|
|
53
|
-
it("PrivateKeySigner produces recoverable EIP-712 signature", async () => {
|
|
54
|
-
const signer = new PrivateKeySigner(ANVIL_PK);
|
|
55
|
-
|
|
56
|
-
const domain = {
|
|
57
|
-
name: "pay",
|
|
58
|
-
version: "0.1",
|
|
59
|
-
chainId: BigInt(TEST_CHAIN_ID),
|
|
60
|
-
verifyingContract: TEST_ROUTER,
|
|
61
|
-
};
|
|
62
|
-
const types = {
|
|
63
|
-
APIRequest: [
|
|
64
|
-
{ name: "method", type: "string" },
|
|
65
|
-
{ name: "path", type: "string" },
|
|
66
|
-
{ name: "timestamp", type: "uint256" },
|
|
67
|
-
{ name: "nonce", type: "bytes32" },
|
|
68
|
-
],
|
|
69
|
-
};
|
|
70
|
-
const nonce =
|
|
71
|
-
"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
|
|
72
|
-
const message = {
|
|
73
|
-
method: "POST",
|
|
74
|
-
path: "/api/v1/direct",
|
|
75
|
-
timestamp: BigInt(1741400000),
|
|
76
|
-
nonce,
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const signature = await signer.signTypedData(domain, types, message);
|
|
80
|
-
|
|
81
|
-
assert.ok(signature.startsWith("0x"), "signature should be hex");
|
|
82
|
-
assert.equal(signature.length, 132, "signature should be 65 bytes hex");
|
|
83
|
-
assert.notEqual(
|
|
84
|
-
signature,
|
|
85
|
-
"0x" + "0".repeat(130),
|
|
86
|
-
"signature must not be zeros (stub)"
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// Recover the signer address from the signature
|
|
90
|
-
const recovered = await recoverAddress({
|
|
91
|
-
hash: (await import("viem")).hashTypedData({
|
|
92
|
-
domain,
|
|
93
|
-
types,
|
|
94
|
-
primaryType: "APIRequest",
|
|
95
|
-
message,
|
|
96
|
-
}),
|
|
97
|
-
signature: signature as Hex,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
assert.equal(
|
|
101
|
-
recovered.toLowerCase(),
|
|
102
|
-
ANVIL_ADDRESS.toLowerCase(),
|
|
103
|
-
"recovered address must match signer"
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe("buildAuthHeaders", () => {
|
|
109
|
-
it("produces valid auth headers with correct address", async () => {
|
|
110
|
-
const headers = await buildAuthHeaders(ANVIL_PK, "POST", "/api/v1/direct", {
|
|
111
|
-
chainId: TEST_CHAIN_ID,
|
|
112
|
-
routerAddress: TEST_ROUTER,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
assert.equal(headers["X-Pay-Agent"], ANVIL_ADDRESS);
|
|
116
|
-
assert.ok(
|
|
117
|
-
headers["X-Pay-Signature"].startsWith("0x"),
|
|
118
|
-
"signature should be hex"
|
|
119
|
-
);
|
|
120
|
-
assert.equal(
|
|
121
|
-
headers["X-Pay-Signature"].length,
|
|
122
|
-
132,
|
|
123
|
-
"signature should be 65 bytes"
|
|
124
|
-
);
|
|
125
|
-
assert.ok(
|
|
126
|
-
Number(headers["X-Pay-Timestamp"]) > 0,
|
|
127
|
-
"timestamp should be positive"
|
|
128
|
-
);
|
|
129
|
-
assert.ok(
|
|
130
|
-
headers["X-Pay-Nonce"].startsWith("0x"),
|
|
131
|
-
"nonce should be hex"
|
|
132
|
-
);
|
|
133
|
-
assert.equal(
|
|
134
|
-
headers["X-Pay-Nonce"].length,
|
|
135
|
-
66,
|
|
136
|
-
"nonce should be 32 bytes hex"
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("signature recovers to the correct address", async () => {
|
|
141
|
-
const headers = await buildAuthHeaders(ANVIL_PK, "POST", "/api/v1/direct", {
|
|
142
|
-
chainId: TEST_CHAIN_ID,
|
|
143
|
-
routerAddress: TEST_ROUTER,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Recompute the hash and recover
|
|
147
|
-
const { hashTypedData } = await import("viem");
|
|
148
|
-
const hash = hashTypedData({
|
|
149
|
-
domain: {
|
|
150
|
-
name: "pay",
|
|
151
|
-
version: "0.1",
|
|
152
|
-
chainId: TEST_CHAIN_ID,
|
|
153
|
-
verifyingContract: TEST_ROUTER,
|
|
154
|
-
},
|
|
155
|
-
types: {
|
|
156
|
-
APIRequest: [
|
|
157
|
-
{ name: "method", type: "string" },
|
|
158
|
-
{ name: "path", type: "string" },
|
|
159
|
-
{ name: "timestamp", type: "uint256" },
|
|
160
|
-
{ name: "nonce", type: "bytes32" },
|
|
161
|
-
],
|
|
162
|
-
},
|
|
163
|
-
primaryType: "APIRequest",
|
|
164
|
-
message: {
|
|
165
|
-
method: "POST",
|
|
166
|
-
path: "/api/v1/direct",
|
|
167
|
-
timestamp: BigInt(headers["X-Pay-Timestamp"]),
|
|
168
|
-
nonce: headers["X-Pay-Nonce"] as Hex,
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const recovered = await recoverAddress({
|
|
173
|
-
hash,
|
|
174
|
-
signature: headers["X-Pay-Signature"] as Hex,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
assert.equal(
|
|
178
|
-
recovered.toLowerCase(),
|
|
179
|
-
ANVIL_ADDRESS.toLowerCase(),
|
|
180
|
-
"recovered address must match signer"
|
|
181
|
-
);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe("computeEip712Hash", () => {
|
|
186
|
-
it("produces deterministic output", () => {
|
|
187
|
-
const nonce =
|
|
188
|
-
"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" as Hex;
|
|
189
|
-
const h1 = computeEip712Hash(
|
|
190
|
-
"POST",
|
|
191
|
-
"/api/v1/direct",
|
|
192
|
-
BigInt(1741400000),
|
|
193
|
-
nonce,
|
|
194
|
-
TEST_CHAIN_ID,
|
|
195
|
-
TEST_ROUTER
|
|
196
|
-
);
|
|
197
|
-
const h2 = computeEip712Hash(
|
|
198
|
-
"POST",
|
|
199
|
-
"/api/v1/direct",
|
|
200
|
-
BigInt(1741400000),
|
|
201
|
-
nonce,
|
|
202
|
-
TEST_CHAIN_ID,
|
|
203
|
-
TEST_ROUTER
|
|
204
|
-
);
|
|
205
|
-
assert.deepEqual(h1, h2);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("different methods produce different hashes", () => {
|
|
209
|
-
const nonce =
|
|
210
|
-
"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" as Hex;
|
|
211
|
-
const h1 = computeEip712Hash(
|
|
212
|
-
"POST",
|
|
213
|
-
"/api/v1/direct",
|
|
214
|
-
BigInt(1741400000),
|
|
215
|
-
nonce,
|
|
216
|
-
TEST_CHAIN_ID,
|
|
217
|
-
TEST_ROUTER
|
|
218
|
-
);
|
|
219
|
-
const h2 = computeEip712Hash(
|
|
220
|
-
"GET",
|
|
221
|
-
"/api/v1/direct",
|
|
222
|
-
BigInt(1741400000),
|
|
223
|
-
nonce,
|
|
224
|
-
TEST_CHAIN_ID,
|
|
225
|
-
TEST_ROUTER
|
|
226
|
-
);
|
|
227
|
-
assert.notDeepEqual(h1, h2);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("different chain IDs produce different hashes", () => {
|
|
231
|
-
const nonce =
|
|
232
|
-
"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" as Hex;
|
|
233
|
-
const h1 = computeEip712Hash(
|
|
234
|
-
"POST",
|
|
235
|
-
"/api/v1/direct",
|
|
236
|
-
BigInt(1741400000),
|
|
237
|
-
nonce,
|
|
238
|
-
8453,
|
|
239
|
-
TEST_ROUTER
|
|
240
|
-
);
|
|
241
|
-
const h2 = computeEip712Hash(
|
|
242
|
-
"POST",
|
|
243
|
-
"/api/v1/direct",
|
|
244
|
-
BigInt(1741400000),
|
|
245
|
-
nonce,
|
|
246
|
-
84531,
|
|
247
|
-
TEST_ROUTER
|
|
248
|
-
);
|
|
249
|
-
assert.notDeepEqual(h1, h2);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Crypto round-trip tests — proves that:
|
|
3
|
+
* 1. Address derivation uses real secp256k1 (not FNV hash)
|
|
4
|
+
* 2. EIP-712 signing produces valid signatures that the server can recover
|
|
5
|
+
* 3. buildAuthHeaders produces valid auth headers
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
11
|
+
import { recoverAddress, type Hex, type Address } from "viem";
|
|
12
|
+
|
|
13
|
+
import { PrivateKeySigner, Wallet } from "../src/wallet.js";
|
|
14
|
+
import { RawKeySigner } from "../src/signer.js";
|
|
15
|
+
import { buildAuthHeaders, computeEip712Hash } from "../src/auth.js";
|
|
16
|
+
|
|
17
|
+
// Anvil account #0 — well-known test key
|
|
18
|
+
const ANVIL_PK =
|
|
19
|
+
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" as Hex;
|
|
20
|
+
const ANVIL_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" as Address;
|
|
21
|
+
|
|
22
|
+
const TEST_ROUTER = "0x5FbDB2315678afecb367f032d93F642f64180aa3" as Address;
|
|
23
|
+
const TEST_CHAIN_ID = 8453;
|
|
24
|
+
|
|
25
|
+
describe("Address derivation", () => {
|
|
26
|
+
it("derives correct address from Anvil #0 private key via Wallet", () => {
|
|
27
|
+
const wallet = new Wallet({
|
|
28
|
+
privateKey: ANVIL_PK,
|
|
29
|
+
chain: "8453",
|
|
30
|
+
apiUrl: "http://localhost:3000/api/v1",
|
|
31
|
+
routerAddress: TEST_ROUTER,
|
|
32
|
+
});
|
|
33
|
+
assert.equal(wallet.address, ANVIL_ADDRESS);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("derives correct address via PrivateKeySigner", () => {
|
|
37
|
+
const signer = new PrivateKeySigner(ANVIL_PK);
|
|
38
|
+
assert.equal(signer.address, ANVIL_ADDRESS);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("derives correct address via RawKeySigner", () => {
|
|
42
|
+
const signer = new RawKeySigner(ANVIL_PK);
|
|
43
|
+
assert.equal(signer.address, ANVIL_ADDRESS);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("works without 0x prefix", () => {
|
|
47
|
+
const signer = new PrivateKeySigner(ANVIL_PK.slice(2));
|
|
48
|
+
assert.equal(signer.address, ANVIL_ADDRESS);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("EIP-712 signing round-trip", () => {
|
|
53
|
+
it("PrivateKeySigner produces recoverable EIP-712 signature", async () => {
|
|
54
|
+
const signer = new PrivateKeySigner(ANVIL_PK);
|
|
55
|
+
|
|
56
|
+
const domain = {
|
|
57
|
+
name: "pay",
|
|
58
|
+
version: "0.1",
|
|
59
|
+
chainId: BigInt(TEST_CHAIN_ID),
|
|
60
|
+
verifyingContract: TEST_ROUTER,
|
|
61
|
+
};
|
|
62
|
+
const types = {
|
|
63
|
+
APIRequest: [
|
|
64
|
+
{ name: "method", type: "string" },
|
|
65
|
+
{ name: "path", type: "string" },
|
|
66
|
+
{ name: "timestamp", type: "uint256" },
|
|
67
|
+
{ name: "nonce", type: "bytes32" },
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
const nonce =
|
|
71
|
+
"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
|
|
72
|
+
const message = {
|
|
73
|
+
method: "POST",
|
|
74
|
+
path: "/api/v1/direct",
|
|
75
|
+
timestamp: BigInt(1741400000),
|
|
76
|
+
nonce,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const signature = await signer.signTypedData(domain, types, message);
|
|
80
|
+
|
|
81
|
+
assert.ok(signature.startsWith("0x"), "signature should be hex");
|
|
82
|
+
assert.equal(signature.length, 132, "signature should be 65 bytes hex");
|
|
83
|
+
assert.notEqual(
|
|
84
|
+
signature,
|
|
85
|
+
"0x" + "0".repeat(130),
|
|
86
|
+
"signature must not be zeros (stub)"
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Recover the signer address from the signature
|
|
90
|
+
const recovered = await recoverAddress({
|
|
91
|
+
hash: (await import("viem")).hashTypedData({
|
|
92
|
+
domain,
|
|
93
|
+
types,
|
|
94
|
+
primaryType: "APIRequest",
|
|
95
|
+
message,
|
|
96
|
+
}),
|
|
97
|
+
signature: signature as Hex,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
assert.equal(
|
|
101
|
+
recovered.toLowerCase(),
|
|
102
|
+
ANVIL_ADDRESS.toLowerCase(),
|
|
103
|
+
"recovered address must match signer"
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("buildAuthHeaders", () => {
|
|
109
|
+
it("produces valid auth headers with correct address", async () => {
|
|
110
|
+
const headers = await buildAuthHeaders(ANVIL_PK, "POST", "/api/v1/direct", {
|
|
111
|
+
chainId: TEST_CHAIN_ID,
|
|
112
|
+
routerAddress: TEST_ROUTER,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
assert.equal(headers["X-Pay-Agent"], ANVIL_ADDRESS);
|
|
116
|
+
assert.ok(
|
|
117
|
+
headers["X-Pay-Signature"].startsWith("0x"),
|
|
118
|
+
"signature should be hex"
|
|
119
|
+
);
|
|
120
|
+
assert.equal(
|
|
121
|
+
headers["X-Pay-Signature"].length,
|
|
122
|
+
132,
|
|
123
|
+
"signature should be 65 bytes"
|
|
124
|
+
);
|
|
125
|
+
assert.ok(
|
|
126
|
+
Number(headers["X-Pay-Timestamp"]) > 0,
|
|
127
|
+
"timestamp should be positive"
|
|
128
|
+
);
|
|
129
|
+
assert.ok(
|
|
130
|
+
headers["X-Pay-Nonce"].startsWith("0x"),
|
|
131
|
+
"nonce should be hex"
|
|
132
|
+
);
|
|
133
|
+
assert.equal(
|
|
134
|
+
headers["X-Pay-Nonce"].length,
|
|
135
|
+
66,
|
|
136
|
+
"nonce should be 32 bytes hex"
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("signature recovers to the correct address", async () => {
|
|
141
|
+
const headers = await buildAuthHeaders(ANVIL_PK, "POST", "/api/v1/direct", {
|
|
142
|
+
chainId: TEST_CHAIN_ID,
|
|
143
|
+
routerAddress: TEST_ROUTER,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Recompute the hash and recover
|
|
147
|
+
const { hashTypedData } = await import("viem");
|
|
148
|
+
const hash = hashTypedData({
|
|
149
|
+
domain: {
|
|
150
|
+
name: "pay",
|
|
151
|
+
version: "0.1",
|
|
152
|
+
chainId: TEST_CHAIN_ID,
|
|
153
|
+
verifyingContract: TEST_ROUTER,
|
|
154
|
+
},
|
|
155
|
+
types: {
|
|
156
|
+
APIRequest: [
|
|
157
|
+
{ name: "method", type: "string" },
|
|
158
|
+
{ name: "path", type: "string" },
|
|
159
|
+
{ name: "timestamp", type: "uint256" },
|
|
160
|
+
{ name: "nonce", type: "bytes32" },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
primaryType: "APIRequest",
|
|
164
|
+
message: {
|
|
165
|
+
method: "POST",
|
|
166
|
+
path: "/api/v1/direct",
|
|
167
|
+
timestamp: BigInt(headers["X-Pay-Timestamp"]),
|
|
168
|
+
nonce: headers["X-Pay-Nonce"] as Hex,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const recovered = await recoverAddress({
|
|
173
|
+
hash,
|
|
174
|
+
signature: headers["X-Pay-Signature"] as Hex,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
assert.equal(
|
|
178
|
+
recovered.toLowerCase(),
|
|
179
|
+
ANVIL_ADDRESS.toLowerCase(),
|
|
180
|
+
"recovered address must match signer"
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("computeEip712Hash", () => {
|
|
186
|
+
it("produces deterministic output", () => {
|
|
187
|
+
const nonce =
|
|
188
|
+
"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" as Hex;
|
|
189
|
+
const h1 = computeEip712Hash(
|
|
190
|
+
"POST",
|
|
191
|
+
"/api/v1/direct",
|
|
192
|
+
BigInt(1741400000),
|
|
193
|
+
nonce,
|
|
194
|
+
TEST_CHAIN_ID,
|
|
195
|
+
TEST_ROUTER
|
|
196
|
+
);
|
|
197
|
+
const h2 = computeEip712Hash(
|
|
198
|
+
"POST",
|
|
199
|
+
"/api/v1/direct",
|
|
200
|
+
BigInt(1741400000),
|
|
201
|
+
nonce,
|
|
202
|
+
TEST_CHAIN_ID,
|
|
203
|
+
TEST_ROUTER
|
|
204
|
+
);
|
|
205
|
+
assert.deepEqual(h1, h2);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("different methods produce different hashes", () => {
|
|
209
|
+
const nonce =
|
|
210
|
+
"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" as Hex;
|
|
211
|
+
const h1 = computeEip712Hash(
|
|
212
|
+
"POST",
|
|
213
|
+
"/api/v1/direct",
|
|
214
|
+
BigInt(1741400000),
|
|
215
|
+
nonce,
|
|
216
|
+
TEST_CHAIN_ID,
|
|
217
|
+
TEST_ROUTER
|
|
218
|
+
);
|
|
219
|
+
const h2 = computeEip712Hash(
|
|
220
|
+
"GET",
|
|
221
|
+
"/api/v1/direct",
|
|
222
|
+
BigInt(1741400000),
|
|
223
|
+
nonce,
|
|
224
|
+
TEST_CHAIN_ID,
|
|
225
|
+
TEST_ROUTER
|
|
226
|
+
);
|
|
227
|
+
assert.notDeepEqual(h1, h2);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("different chain IDs produce different hashes", () => {
|
|
231
|
+
const nonce =
|
|
232
|
+
"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" as Hex;
|
|
233
|
+
const h1 = computeEip712Hash(
|
|
234
|
+
"POST",
|
|
235
|
+
"/api/v1/direct",
|
|
236
|
+
BigInt(1741400000),
|
|
237
|
+
nonce,
|
|
238
|
+
8453,
|
|
239
|
+
TEST_ROUTER
|
|
240
|
+
);
|
|
241
|
+
const h2 = computeEip712Hash(
|
|
242
|
+
"POST",
|
|
243
|
+
"/api/v1/direct",
|
|
244
|
+
BigInt(1741400000),
|
|
245
|
+
nonce,
|
|
246
|
+
84531,
|
|
247
|
+
TEST_ROUTER
|
|
248
|
+
);
|
|
249
|
+
assert.notDeepEqual(h1, h2);
|
|
250
|
+
});
|
|
251
|
+
});
|