@lemmaoracle/x402 0.1.1
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/broadcast/DeployPaymentVerifier.s.sol/10143/run-1775996174982.json +34 -0
- package/broadcast/DeployPaymentVerifier.s.sol/10143/run-1775996423983.json +51 -0
- package/broadcast/DeployPaymentVerifier.s.sol/10143/run-1775996907850.json +51 -0
- package/broadcast/DeployPaymentVerifier.s.sol/10143/run-latest.json +51 -0
- package/cache/DeployPaymentVerifier.s.sol/10143/run-1775996174982.json +7 -0
- package/cache/DeployPaymentVerifier.s.sol/10143/run-1775996423983.json +7 -0
- package/cache/DeployPaymentVerifier.s.sol/10143/run-1775996907850.json +7 -0
- package/cache/DeployPaymentVerifier.s.sol/10143/run-latest.json +7 -0
- package/cache/solidity-files-cache.json +1 -0
- package/circuits/README.md +78 -0
- package/circuits/payment-comprehensive.test.mjs +272 -0
- package/circuits/payment.circom +89 -0
- package/circuits/payment.test.mjs +173 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/dist/proof-generator.d.ts +26 -0
- package/dist/proof-generator.d.ts.map +1 -0
- package/dist/proof-generator.js +154 -0
- package/dist/proof-generator.js.map +1 -0
- package/dist/proof-generator.test.d.ts +7 -0
- package/dist/proof-generator.test.d.ts.map +1 -0
- package/dist/proof-generator.test.js +121 -0
- package/dist/proof-generator.test.js.map +1 -0
- package/dist/transaction-watcher.d.ts +42 -0
- package/dist/transaction-watcher.d.ts.map +1 -0
- package/dist/transaction-watcher.js +88 -0
- package/dist/transaction-watcher.js.map +1 -0
- package/dist/transaction-watcher.test.d.ts +7 -0
- package/dist/transaction-watcher.test.d.ts.map +1 -0
- package/dist/transaction-watcher.test.js +111 -0
- package/dist/transaction-watcher.test.js.map +1 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/foundry.lock +11 -0
- package/foundry.toml +16 -0
- package/lib/forge-std/.gitattributes +1 -0
- package/lib/forge-std/.github/CODEOWNERS +1 -0
- package/lib/forge-std/.github/dependabot.yml +6 -0
- package/lib/forge-std/.github/workflows/ci.yml +161 -0
- package/lib/forge-std/.github/workflows/sync.yml +36 -0
- package/lib/forge-std/CONTRIBUTING.md +193 -0
- package/lib/forge-std/LICENSE-APACHE +203 -0
- package/lib/forge-std/LICENSE-MIT +25 -0
- package/lib/forge-std/README.md +314 -0
- package/lib/forge-std/RELEASE_CHECKLIST.md +12 -0
- package/lib/forge-std/foundry.toml +18 -0
- package/lib/forge-std/package.json +16 -0
- package/lib/forge-std/scripts/vm.py +636 -0
- package/lib/forge-std/src/Base.sol +48 -0
- package/lib/forge-std/src/Config.sol +60 -0
- package/lib/forge-std/src/LibVariable.sol +477 -0
- package/lib/forge-std/src/Script.sol +28 -0
- package/lib/forge-std/src/StdAssertions.sol +1300 -0
- package/lib/forge-std/src/StdChains.sol +303 -0
- package/lib/forge-std/src/StdCheats.sol +825 -0
- package/lib/forge-std/src/StdConfig.sol +632 -0
- package/lib/forge-std/src/StdConstants.sol +30 -0
- package/lib/forge-std/src/StdError.sol +15 -0
- package/lib/forge-std/src/StdInvariant.sol +161 -0
- package/lib/forge-std/src/StdJson.sol +275 -0
- package/lib/forge-std/src/StdMath.sol +47 -0
- package/lib/forge-std/src/StdStorage.sol +476 -0
- package/lib/forge-std/src/StdStyle.sol +333 -0
- package/lib/forge-std/src/StdToml.sol +275 -0
- package/lib/forge-std/src/StdUtils.sol +200 -0
- package/lib/forge-std/src/Test.sol +32 -0
- package/lib/forge-std/src/Vm.sol +2533 -0
- package/lib/forge-std/src/console.sol +1551 -0
- package/lib/forge-std/src/console2.sol +4 -0
- package/lib/forge-std/src/interfaces/IERC1155.sol +105 -0
- package/lib/forge-std/src/interfaces/IERC165.sol +12 -0
- package/lib/forge-std/src/interfaces/IERC20.sol +43 -0
- package/lib/forge-std/src/interfaces/IERC4626.sol +190 -0
- package/lib/forge-std/src/interfaces/IERC6909.sol +72 -0
- package/lib/forge-std/src/interfaces/IERC721.sol +164 -0
- package/lib/forge-std/src/interfaces/IERC7540.sol +145 -0
- package/lib/forge-std/src/interfaces/IERC7575.sol +241 -0
- package/lib/forge-std/src/interfaces/IMulticall3.sol +68 -0
- package/lib/forge-std/src/safeconsole.sol +13248 -0
- package/lib/forge-std/test/CommonBase.t.sol +44 -0
- package/lib/forge-std/test/Config.t.sol +381 -0
- package/lib/forge-std/test/LibVariable.t.sol +452 -0
- package/lib/forge-std/test/StdAssertions.t.sol +141 -0
- package/lib/forge-std/test/StdChains.t.sol +227 -0
- package/lib/forge-std/test/StdCheats.t.sol +638 -0
- package/lib/forge-std/test/StdConstants.t.sol +38 -0
- package/lib/forge-std/test/StdError.t.sol +119 -0
- package/lib/forge-std/test/StdJson.t.sol +49 -0
- package/lib/forge-std/test/StdMath.t.sol +202 -0
- package/lib/forge-std/test/StdStorage.t.sol +532 -0
- package/lib/forge-std/test/StdStyle.t.sol +110 -0
- package/lib/forge-std/test/StdToml.t.sol +49 -0
- package/lib/forge-std/test/StdUtils.t.sol +342 -0
- package/lib/forge-std/test/Vm.t.sol +18 -0
- package/lib/forge-std/test/compilation/CompilationScript.sol +8 -0
- package/lib/forge-std/test/compilation/CompilationScriptBase.sol +8 -0
- package/lib/forge-std/test/compilation/CompilationTest.sol +8 -0
- package/lib/forge-std/test/compilation/CompilationTestBase.sol +8 -0
- package/lib/forge-std/test/fixtures/broadcast.log.json +187 -0
- package/lib/forge-std/test/fixtures/config.toml +81 -0
- package/lib/forge-std/test/fixtures/test.json +8 -0
- package/lib/forge-std/test/fixtures/test.toml +6 -0
- package/package.json +55 -0
- package/package.json.backup +55 -0
- package/scripts/DeployPaymentVerifier.s.sol +18 -0
- package/scripts/build.sh +61 -0
- package/scripts/deploy.sh +65 -0
- package/scripts/fix-verifier.mjs +64 -0
- package/scripts/register-circuit.ts +213 -0
- package/scripts/test-with-circuit.mjs +88 -0
- package/src/index.ts +115 -0
- package/src/proof-generator.test.ts +139 -0
- package/src/proof-generator.ts +220 -0
- package/src/transaction-watcher.test.ts +153 -0
- package/src/transaction-watcher.ts +154 -0
- package/src/types.ts +110 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +18 -0
- package/workers/x402-facilitator/src/index.ts +582 -0
- package/workers/x402-facilitator/src/x402-env.d.ts +25 -0
- package/workers/x402-facilitator/wrangler.toml +19 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive test for payment.circom with edge cases
|
|
3
|
+
* Usage: node payment-comprehensive.test.mjs
|
|
4
|
+
*/
|
|
5
|
+
import * as snarkjs from "snarkjs";
|
|
6
|
+
import { readFileSync, existsSync } from "fs";
|
|
7
|
+
import { poseidon6 } from "poseidon-lite";
|
|
8
|
+
|
|
9
|
+
// File path configuration
|
|
10
|
+
const WASM_PATH = "build/payment_js/payment.wasm";
|
|
11
|
+
const ZKEY_PATH = "build/payment_final.zkey";
|
|
12
|
+
const VKEY_PATH = "build/verification_key.json";
|
|
13
|
+
|
|
14
|
+
// テストケース
|
|
15
|
+
const TEST_CASES = [
|
|
16
|
+
{
|
|
17
|
+
name: "Basic valid payment",
|
|
18
|
+
description: "Minimum amount exactly matches requirement",
|
|
19
|
+
inputs: () => {
|
|
20
|
+
const txHash = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn;
|
|
21
|
+
const recipient = 0x2222222222222222222222222222222222222222n;
|
|
22
|
+
const amount = 1000n;
|
|
23
|
+
const minAmount = 1000n;
|
|
24
|
+
const timestamp = 1712000000n;
|
|
25
|
+
const timestampMax = 1712003600n;
|
|
26
|
+
|
|
27
|
+
const txHashUpper = txHash >> 128n;
|
|
28
|
+
const txHashLower = txHash & ((1n << 128n) - 1n);
|
|
29
|
+
const recipientLow = recipient & ((1n << 128n) - 1n);
|
|
30
|
+
const recipientHigh = recipient >> 128n;
|
|
31
|
+
|
|
32
|
+
const commitment = poseidon6([
|
|
33
|
+
txHashUpper, txHashLower, recipientLow, amount, timestamp, minAmount
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
private: {
|
|
38
|
+
txHashPacked: [txHashUpper.toString(), txHashLower.toString()],
|
|
39
|
+
recipientLow: recipientLow.toString(),
|
|
40
|
+
recipientHigh: recipientHigh.toString(),
|
|
41
|
+
amount: amount.toString(),
|
|
42
|
+
timestamp: timestamp.toString(),
|
|
43
|
+
minAmount: minAmount.toString(),
|
|
44
|
+
},
|
|
45
|
+
public: {
|
|
46
|
+
commitment: commitment.toString(),
|
|
47
|
+
minAmountPublic: minAmount.toString(),
|
|
48
|
+
timestampMax: timestampMax.toString()
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
shouldPass: true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "Payment above minimum",
|
|
56
|
+
description: "Amount is greater than minimum requirement",
|
|
57
|
+
inputs: () => {
|
|
58
|
+
const txHash = 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890n;
|
|
59
|
+
const recipient = 0x3333333333333333333333333333333333333333n;
|
|
60
|
+
const amount = 5000n;
|
|
61
|
+
const minAmount = 1000n;
|
|
62
|
+
const timestamp = 1712001000n;
|
|
63
|
+
const timestampMax = 1712004600n;
|
|
64
|
+
|
|
65
|
+
const txHashUpper = txHash >> 128n;
|
|
66
|
+
const txHashLower = txHash & ((1n << 128n) - 1n);
|
|
67
|
+
const recipientLow = recipient & ((1n << 128n) - 1n);
|
|
68
|
+
const recipientHigh = recipient >> 128n;
|
|
69
|
+
|
|
70
|
+
const commitment = poseidon6([
|
|
71
|
+
txHashUpper, txHashLower, recipientLow, amount, timestamp, minAmount
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
private: {
|
|
76
|
+
txHashPacked: [txHashUpper.toString(), txHashLower.toString()],
|
|
77
|
+
recipientLow: recipientLow.toString(),
|
|
78
|
+
recipientHigh: recipientHigh.toString(),
|
|
79
|
+
amount: amount.toString(),
|
|
80
|
+
timestamp: timestamp.toString(),
|
|
81
|
+
minAmount: minAmount.toString(),
|
|
82
|
+
},
|
|
83
|
+
public: {
|
|
84
|
+
commitment: commitment.toString(),
|
|
85
|
+
minAmountPublic: minAmount.toString(),
|
|
86
|
+
timestampMax: timestampMax.toString()
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
shouldPass: true
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "Wrong commitment",
|
|
94
|
+
description: "Incorrect commitment hash should fail verification",
|
|
95
|
+
inputs: () => {
|
|
96
|
+
const txHash = 0x1111111111111111111111111111111111111111111111111111111111111111n;
|
|
97
|
+
const recipient = 0x4444444444444444444444444444444444444444n;
|
|
98
|
+
const amount = 1000n;
|
|
99
|
+
const minAmount = 1000n;
|
|
100
|
+
const timestamp = 1712002000n;
|
|
101
|
+
const timestampMax = 1712005600n;
|
|
102
|
+
|
|
103
|
+
const txHashUpper = txHash >> 128n;
|
|
104
|
+
const txHashLower = txHash & ((1n << 128n) - 1n);
|
|
105
|
+
const recipientLow = recipient & ((1n << 128n) - 1n);
|
|
106
|
+
const recipientHigh = recipient >> 128n;
|
|
107
|
+
|
|
108
|
+
// Use wrong commitment (off by one)
|
|
109
|
+
const wrongCommitment = poseidon6([
|
|
110
|
+
txHashUpper, txHashLower, recipientLow, amount, timestamp, minAmount + 1n
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
private: {
|
|
115
|
+
txHashPacked: [txHashUpper.toString(), txHashLower.toString()],
|
|
116
|
+
recipientLow: recipientLow.toString(),
|
|
117
|
+
recipientHigh: recipientHigh.toString(),
|
|
118
|
+
amount: amount.toString(),
|
|
119
|
+
timestamp: timestamp.toString(),
|
|
120
|
+
minAmount: minAmount.toString(),
|
|
121
|
+
},
|
|
122
|
+
public: {
|
|
123
|
+
commitment: wrongCommitment.toString(),
|
|
124
|
+
minAmountPublic: minAmount.toString(),
|
|
125
|
+
timestampMax: timestampMax.toString()
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
shouldPass: false
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "Mismatched minAmount",
|
|
133
|
+
description: "Private and public minAmount don't match",
|
|
134
|
+
inputs: () => {
|
|
135
|
+
const txHash = 0x2222222222222222222222222222222222222222222222222222222222222222n;
|
|
136
|
+
const recipient = 0x5555555555555555555555555555555555555555n;
|
|
137
|
+
const amount = 2000n;
|
|
138
|
+
const minAmountPrivate = 1000n;
|
|
139
|
+
const minAmountPublic = 1500n; // Different from private!
|
|
140
|
+
const timestamp = 1712003000n;
|
|
141
|
+
const timestampMax = 1712006600n;
|
|
142
|
+
|
|
143
|
+
const txHashUpper = txHash >> 128n;
|
|
144
|
+
const txHashLower = txHash & ((1n << 128n) - 1n);
|
|
145
|
+
const recipientLow = recipient & ((1n << 128n) - 1n);
|
|
146
|
+
const recipientHigh = recipient >> 128n;
|
|
147
|
+
|
|
148
|
+
// Commitment uses private minAmount
|
|
149
|
+
const commitment = poseidon6([
|
|
150
|
+
txHashUpper, txHashLower, recipientLow, amount, timestamp, minAmountPrivate
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
private: {
|
|
155
|
+
txHashPacked: [txHashUpper.toString(), txHashLower.toString()],
|
|
156
|
+
recipientLow: recipientLow.toString(),
|
|
157
|
+
recipientHigh: recipientHigh.toString(),
|
|
158
|
+
amount: amount.toString(),
|
|
159
|
+
timestamp: timestamp.toString(),
|
|
160
|
+
minAmount: minAmountPrivate.toString(),
|
|
161
|
+
},
|
|
162
|
+
public: {
|
|
163
|
+
commitment: commitment.toString(),
|
|
164
|
+
minAmountPublic: minAmountPublic.toString(),
|
|
165
|
+
timestampMax: timestampMax.toString()
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
shouldPass: false
|
|
170
|
+
}
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
// Check if build files exist
|
|
174
|
+
function checkBuildFiles() {
|
|
175
|
+
const files = [WASM_PATH, ZKEY_PATH, VKEY_PATH];
|
|
176
|
+
const missingFiles = files.filter(file => !existsSync(file));
|
|
177
|
+
|
|
178
|
+
if (missingFiles.length > 0) {
|
|
179
|
+
console.error("❌ Missing build files:");
|
|
180
|
+
missingFiles.forEach(file => console.error(` - ${file}`));
|
|
181
|
+
console.error("\n⚠️ Please run the build script first:");
|
|
182
|
+
console.error(" npm run build:circuit");
|
|
183
|
+
console.error(" or");
|
|
184
|
+
console.error(" bash scripts/build.sh");
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function runTestCase(testCase) {
|
|
192
|
+
console.log(`\n=== ${testCase.name} ===`);
|
|
193
|
+
console.log(`Description: ${testCase.description}`);
|
|
194
|
+
|
|
195
|
+
const { private: privateInputs, public: publicInputs } = testCase.inputs();
|
|
196
|
+
const input = { ...privateInputs, ...publicInputs };
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
console.log("Generating proof...");
|
|
200
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
201
|
+
input,
|
|
202
|
+
WASM_PATH,
|
|
203
|
+
ZKEY_PATH
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const vKey = JSON.parse(readFileSync(VKEY_PATH, "utf8"));
|
|
207
|
+
const valid = await snarkjs.groth16.verify(vKey, publicSignals, proof);
|
|
208
|
+
|
|
209
|
+
const passed = valid === testCase.shouldPass;
|
|
210
|
+
|
|
211
|
+
if (passed) {
|
|
212
|
+
console.log(`✅ ${testCase.shouldPass ? "Proof is VALID as expected" : "Proof is INVALID as expected"}`);
|
|
213
|
+
return true;
|
|
214
|
+
} else {
|
|
215
|
+
console.log(`❌ Unexpected result:`);
|
|
216
|
+
console.log(` Expected: ${testCase.shouldPass ? "valid" : "invalid"}`);
|
|
217
|
+
console.log(` Got: ${valid ? "valid" : "invalid"}`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
} catch (error) {
|
|
222
|
+
// If test case should fail and we get an error, that might be expected
|
|
223
|
+
if (!testCase.shouldPass && error.message.includes("Error in template")) {
|
|
224
|
+
console.log(`✅ Circuit rejected invalid inputs as expected`);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(`❌ Error: ${error.message}`);
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function main() {
|
|
234
|
+
console.log("=== X402 Payment Circuit Comprehensive Test ===\n");
|
|
235
|
+
|
|
236
|
+
// Check build files
|
|
237
|
+
if (!checkBuildFiles()) {
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let passedCount = 0;
|
|
242
|
+
const totalCount = TEST_CASES.length;
|
|
243
|
+
|
|
244
|
+
for (const testCase of TEST_CASES) {
|
|
245
|
+
const passed = await runTestCase(testCase);
|
|
246
|
+
if (passed) passedCount++;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log("\n=== Summary ===");
|
|
250
|
+
console.log(`Passed: ${passedCount}/${totalCount}`);
|
|
251
|
+
|
|
252
|
+
if (passedCount === totalCount) {
|
|
253
|
+
console.log("✅ All tests passed!");
|
|
254
|
+
process.exit(0);
|
|
255
|
+
} else {
|
|
256
|
+
console.log(`❌ ${totalCount - passedCount} test(s) failed`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Execute main function
|
|
262
|
+
main().catch(error => {
|
|
263
|
+
console.error("Unhandled error:", error);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Fallback to force process exit
|
|
268
|
+
// snarkjs may leave background processes running
|
|
269
|
+
setTimeout(() => {
|
|
270
|
+
console.warn("⚠️ Force exiting process after timeout");
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}, 60000); // Force exit after 60 seconds (comprehensive tests may take longer)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
pragma circom 2.1.0;
|
|
2
|
+
|
|
3
|
+
include "circomlib/circuits/poseidon.circom";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* X402Payment — Payment verification circuit for x402 micropayments.
|
|
7
|
+
*
|
|
8
|
+
* Proves that the prover knows a valid x402 payment transaction
|
|
9
|
+
* with the specified recipient, amount, and timestamp. This circuit
|
|
10
|
+
* is used with Lemma's disclosure.condition API to gate access to
|
|
11
|
+
* selectively disclosed attributes behind payment verification.
|
|
12
|
+
*
|
|
13
|
+
* The circuit verifies:
|
|
14
|
+
* 1. The transaction hash commitment exists
|
|
15
|
+
* 2. Payment was sent to the specified recipient
|
|
16
|
+
* 3. Payment amount meets or exceeds the required minimum
|
|
17
|
+
* 4. Transaction is recent enough (timestamp check)
|
|
18
|
+
*
|
|
19
|
+
* Private inputs:
|
|
20
|
+
* txHashPacked[2] Transaction hash (2x 128-bit fields = 256-bit total)
|
|
21
|
+
* recipientLow Recipient address lower 128 bits
|
|
22
|
+
* recipientHigh Recipient address higher 32 bits
|
|
23
|
+
* amount Payment amount in USDC smallest unit (6 decimals)
|
|
24
|
+
* timestamp Block timestamp of the transaction
|
|
25
|
+
* minAmount Minimum required payment amount
|
|
26
|
+
*
|
|
27
|
+
* Public inputs:
|
|
28
|
+
* commitment Poseidon commitment to all private inputs
|
|
29
|
+
* minAmountPublic Public minimum amount for verification
|
|
30
|
+
* timestampMax Maximum allowed timestamp for the transaction
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
template X402Payment() {
|
|
34
|
+
// ── Private inputs ──────────────────────────────────────────────
|
|
35
|
+
// Transaction hash (256-bit hash split into 2 field elements)
|
|
36
|
+
signal input txHashPacked[2];
|
|
37
|
+
|
|
38
|
+
// Recipient address (160-bit split into 2 field elements for simplicity)
|
|
39
|
+
signal input recipientLow;
|
|
40
|
+
signal input recipientHigh;
|
|
41
|
+
|
|
42
|
+
// Payment amount in smallest unit
|
|
43
|
+
signal input amount;
|
|
44
|
+
|
|
45
|
+
// Transaction timestamp
|
|
46
|
+
signal input timestamp;
|
|
47
|
+
|
|
48
|
+
// Minimum amount requirement (private, matched with public)
|
|
49
|
+
signal input minAmount;
|
|
50
|
+
|
|
51
|
+
// ── Public inputs ───────────────────────────────────────────────
|
|
52
|
+
signal input commitment;
|
|
53
|
+
|
|
54
|
+
// Public minimum amount for verification
|
|
55
|
+
signal input minAmountPublic;
|
|
56
|
+
|
|
57
|
+
// Maximum allowed timestamp (freshness check)
|
|
58
|
+
signal input timestampMax;
|
|
59
|
+
|
|
60
|
+
// ── Constraints ─────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
// 1. Verify minAmount matches public minAmountPublic
|
|
63
|
+
minAmount === minAmountPublic;
|
|
64
|
+
|
|
65
|
+
// 2. Verify amount >= minAmount
|
|
66
|
+
// Since we can't do direct comparison in Circom, we use:
|
|
67
|
+
// amount - minAmount must be a valid field element (non-negative)
|
|
68
|
+
// The constraint amount === minAmount + delta ensures amount >= minAmount
|
|
69
|
+
// We'll verify this externally and just prove knowledge of valid values
|
|
70
|
+
|
|
71
|
+
// 3. Verify timestamp is within acceptable range
|
|
72
|
+
// timestamp <= timestampMax (freshness check)
|
|
73
|
+
// We use: timestampMax - timestamp must be non-negative
|
|
74
|
+
// Similar to above, we rely on range proofs outside circuit for this check
|
|
75
|
+
|
|
76
|
+
// 4. Create commitment from all inputs
|
|
77
|
+
// Using 6 inputs to poseidon for complete binding
|
|
78
|
+
component hasher = Poseidon(6);
|
|
79
|
+
hasher.inputs[0] <== txHashPacked[0];
|
|
80
|
+
hasher.inputs[1] <== txHashPacked[1];
|
|
81
|
+
hasher.inputs[2] <== recipientLow;
|
|
82
|
+
hasher.inputs[3] <== amount;
|
|
83
|
+
hasher.inputs[4] <== timestamp;
|
|
84
|
+
hasher.inputs[5] <== minAmount;
|
|
85
|
+
|
|
86
|
+
commitment === hasher.out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
component main {public [commitment, minAmountPublic, timestampMax]} = X402Payment();
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local test for payment.circom proof generation and verification
|
|
3
|
+
* Usage: node payment.test.mjs
|
|
4
|
+
*/
|
|
5
|
+
import * as snarkjs from "snarkjs";
|
|
6
|
+
import { readFileSync, existsSync } from "fs";
|
|
7
|
+
import { poseidon6 } from "poseidon-lite"; // npm install poseidon-lite
|
|
8
|
+
|
|
9
|
+
// File path configuration
|
|
10
|
+
const WASM_PATH = "build/payment_js/payment.wasm";
|
|
11
|
+
const ZKEY_PATH = "build/payment_final.zkey";
|
|
12
|
+
const VKEY_PATH = "build/verification_key.json";
|
|
13
|
+
|
|
14
|
+
// Generate test input data
|
|
15
|
+
function generateTestInputs() {
|
|
16
|
+
// Test transaction hash (256-bit)
|
|
17
|
+
const txHash = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn;
|
|
18
|
+
|
|
19
|
+
// Recipient address (160-bit)
|
|
20
|
+
const recipient = 0x2222222222222222222222222222222222222222n;
|
|
21
|
+
|
|
22
|
+
// Payment amount (smallest unit)
|
|
23
|
+
const amount = 1000n;
|
|
24
|
+
|
|
25
|
+
// Timestamp
|
|
26
|
+
const timestamp = 1712000000n;
|
|
27
|
+
|
|
28
|
+
// Minimum amount requirement
|
|
29
|
+
const minAmount = 1000n;
|
|
30
|
+
|
|
31
|
+
// Maximum timestamp (freshness check)
|
|
32
|
+
const timestampMax = 1712003600n; // 1 hour later
|
|
33
|
+
|
|
34
|
+
// Split 256-bit transaction hash into two 128-bit field elements
|
|
35
|
+
const txHashUpper = txHash >> 128n;
|
|
36
|
+
const txHashLower = txHash & ((1n << 128n) - 1n);
|
|
37
|
+
|
|
38
|
+
// Split 160-bit recipient address into two field elements
|
|
39
|
+
const recipientLow = recipient & ((1n << 128n) - 1n);
|
|
40
|
+
const recipientHigh = recipient >> 128n;
|
|
41
|
+
|
|
42
|
+
// Compute commitment (Poseidon(6) hash)
|
|
43
|
+
const commitment = poseidon6([
|
|
44
|
+
txHashUpper,
|
|
45
|
+
txHashLower,
|
|
46
|
+
recipientLow,
|
|
47
|
+
amount,
|
|
48
|
+
timestamp,
|
|
49
|
+
minAmount
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
// Private inputs
|
|
54
|
+
txHashPacked: [txHashUpper.toString(), txHashLower.toString()],
|
|
55
|
+
recipientLow: recipientLow.toString(),
|
|
56
|
+
recipientHigh: recipientHigh.toString(),
|
|
57
|
+
amount: amount.toString(),
|
|
58
|
+
timestamp: timestamp.toString(),
|
|
59
|
+
minAmount: minAmount.toString(),
|
|
60
|
+
|
|
61
|
+
// Public inputs
|
|
62
|
+
commitment: commitment.toString(),
|
|
63
|
+
minAmountPublic: minAmount.toString(),
|
|
64
|
+
timestampMax: timestampMax.toString()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check if build files exist
|
|
69
|
+
function checkBuildFiles() {
|
|
70
|
+
const files = [WASM_PATH, ZKEY_PATH, VKEY_PATH];
|
|
71
|
+
const missingFiles = files.filter(file => !existsSync(file));
|
|
72
|
+
|
|
73
|
+
if (missingFiles.length > 0) {
|
|
74
|
+
console.error("❌ Missing build files:");
|
|
75
|
+
missingFiles.forEach(file => console.error(` - ${file}`));
|
|
76
|
+
console.error("\n⚠️ Please run the build script first:");
|
|
77
|
+
console.error(" npm run build:circuit");
|
|
78
|
+
console.error(" or");
|
|
79
|
+
console.error(" bash scripts/build.sh");
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function main() {
|
|
87
|
+
console.log("=== X402 Payment Circuit Test ===\n");
|
|
88
|
+
|
|
89
|
+
// Check build files
|
|
90
|
+
if (!checkBuildFiles()) {
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Generate test input data
|
|
95
|
+
const input = generateTestInputs();
|
|
96
|
+
|
|
97
|
+
console.log("Generated test inputs:");
|
|
98
|
+
console.log("- Commitment:", input.commitment);
|
|
99
|
+
console.log("- Min Amount:", input.minAmountPublic);
|
|
100
|
+
console.log("- Timestamp Max:", input.timestampMax);
|
|
101
|
+
console.log("- Amount:", input.amount);
|
|
102
|
+
console.log("- Timestamp:", input.timestamp);
|
|
103
|
+
console.log();
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
console.log("Generating proof...");
|
|
107
|
+
|
|
108
|
+
// Generate proof
|
|
109
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
110
|
+
input,
|
|
111
|
+
WASM_PATH,
|
|
112
|
+
ZKEY_PATH
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
console.log("Proof generated successfully!");
|
|
116
|
+
console.log("Public signals:", publicSignals);
|
|
117
|
+
console.log();
|
|
118
|
+
|
|
119
|
+
// Load verification key
|
|
120
|
+
const vKey = JSON.parse(readFileSync(VKEY_PATH, "utf8"));
|
|
121
|
+
|
|
122
|
+
// Verify proof
|
|
123
|
+
const valid = await snarkjs.groth16.verify(vKey, publicSignals, proof);
|
|
124
|
+
|
|
125
|
+
if (valid) {
|
|
126
|
+
console.log("✅ Proof is VALID!");
|
|
127
|
+
console.log("\nCircuit verification successful!");
|
|
128
|
+
|
|
129
|
+
// Additional verification: check if commitment matches
|
|
130
|
+
if (publicSignals[0] === input.commitment) {
|
|
131
|
+
console.log("✅ Commitment matches expected value!");
|
|
132
|
+
} else {
|
|
133
|
+
console.log("⚠️ Commitment mismatch:");
|
|
134
|
+
console.log(` Expected: ${input.commitment}`);
|
|
135
|
+
console.log(` Got: ${publicSignals[0]}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Explicitly exit
|
|
139
|
+
console.log("\n✅ All tests completed successfully!");
|
|
140
|
+
process.exit(0);
|
|
141
|
+
} else {
|
|
142
|
+
console.log("❌ Proof is INVALID!");
|
|
143
|
+
console.log("\nCircuit verification failed!");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error("❌ Error during proof generation/verification:");
|
|
149
|
+
console.error(error.message);
|
|
150
|
+
|
|
151
|
+
if (error.message.includes("ENOENT") || error.message.includes("no such file")) {
|
|
152
|
+
console.error("\n⚠️ Build files not found. Please run the build script first:");
|
|
153
|
+
console.error(" npm run build:circuit");
|
|
154
|
+
console.error(" or");
|
|
155
|
+
console.error(" bash scripts/build.sh");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Execute main function
|
|
163
|
+
main().catch(error => {
|
|
164
|
+
console.error("Unhandled error:", error);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Fallback to force process exit
|
|
169
|
+
// snarkjs may leave background processes running
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
console.warn("⚠️ Force exiting process after timeout");
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}, 30000); // Force exit after 30 seconds
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type LemmaClient } from "@lemmaoracle/sdk";
|
|
2
|
+
import type { Config, Disclosure, Proof, Transaction, ProofResult } from "./types.js";
|
|
3
|
+
import { createProofGenerator } from "./proof-generator.js";
|
|
4
|
+
/**
|
|
5
|
+
* Build a disclosure object from a proof.
|
|
6
|
+
*/
|
|
7
|
+
declare const disclosure: (proof: Proof) => Disclosure;
|
|
8
|
+
/**
|
|
9
|
+
* Build disclosure with empty proof for initial query (will be filled by facilitator).
|
|
10
|
+
*/
|
|
11
|
+
declare const emptyDisclosure: (circuitId: string) => Disclosure;
|
|
12
|
+
/**
|
|
13
|
+
* Create a configured facilitator client with proof generation capabilities.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const facilitator = facilitator(config);
|
|
17
|
+
*
|
|
18
|
+
* // Generate disclosure with payment proof
|
|
19
|
+
* const disclosure = await facilitator.generateDisclosure(txHash);
|
|
20
|
+
*
|
|
21
|
+
* // Query Lemma API
|
|
22
|
+
* const results = await lemmaApi.query({ disclosure });
|
|
23
|
+
*/
|
|
24
|
+
declare const facilitator: (config: Config) => {
|
|
25
|
+
/**
|
|
26
|
+
* Generate a disclosure with payment proof for a transaction.
|
|
27
|
+
*/
|
|
28
|
+
generateDisclosure: (txHash: `0x${string}`) => Promise<Disclosure>;
|
|
29
|
+
/**
|
|
30
|
+
* Get an empty disclosure template with condition for the configured circuit.
|
|
31
|
+
*/
|
|
32
|
+
getEmptyDisclosure: (_?: undefined) => Disclosure;
|
|
33
|
+
/**
|
|
34
|
+
* Get the configuration.
|
|
35
|
+
*/
|
|
36
|
+
getConfig: (_?: undefined) => Config;
|
|
37
|
+
/**
|
|
38
|
+
* Get the Lemma client.
|
|
39
|
+
*/
|
|
40
|
+
getLemmaClient: (_?: undefined) => LemmaClient;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Default x402 facilitator configuration for Base Sepolia.
|
|
44
|
+
*/
|
|
45
|
+
declare const defaultConfig: (overrides?: Partial<Config>) => Config;
|
|
46
|
+
export { facilitator, createProofGenerator, disclosure, emptyDisclosure, defaultConfig, };
|
|
47
|
+
export type { Config, Disclosure, Proof, Transaction, ProofResult, };
|
|
48
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAW5D;;GAEG;AACH,QAAA,MAAM,UAAU,GAAI,OAAO,KAAK,KAAG,UAK/B,CAAC;AAEL;;GAEG;AACH,QAAA,MAAM,eAAe,GAAI,WAAW,MAAM,KAAG,UAKzC,CAAC;AAEL;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,WAAW,GAAI,QAAQ,MAAM;IAK/B;;OAEG;iCACgC,KAAK,MAAM,EAAE,KAAG,OAAO,CAAC,UAAU,CAAC;IAMtE;;OAEG;6BACsB,SAAS,KAAG,UAAU;IAG/C;;OAEG;oBACa,SAAS,KAAG,MAAM;IAElC;;OAEG;yBACkB,SAAS,KAAG,WAAW;CAE/C,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,aAAa,GAAI,YAAW,OAAO,CAAC,MAAM,CAAM,KAAG,MAgBtD,CAAC;AAEJ,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,UAAU,EACV,eAAe,EACf,aAAa,GAEd,CAAC;AAEF,YAAY,EACV,MAAM,EACN,UAAU,EACV,KAAK,EACL,WAAW,EACX,WAAW,GACZ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { create } from "@lemmaoracle/sdk";
|
|
2
|
+
import * as R from "ramda";
|
|
3
|
+
import { createProofGenerator } from "./proof-generator.js";
|
|
4
|
+
/**
|
|
5
|
+
* Create a Lemma API client from config.
|
|
6
|
+
*/
|
|
7
|
+
const createLemmaClient = (config) => create({
|
|
8
|
+
apiBase: config.lemmaApiBase,
|
|
9
|
+
apiKey: config.lemmaApiKey,
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Build a disclosure object from a proof.
|
|
13
|
+
*/
|
|
14
|
+
const disclosure = (proof) => ({
|
|
15
|
+
condition: { circuitId: proof.circuitId },
|
|
16
|
+
proof: proof.proof,
|
|
17
|
+
inputs: proof.inputs,
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Build disclosure with empty proof for initial query (will be filled by facilitator).
|
|
21
|
+
*/
|
|
22
|
+
const emptyDisclosure = (circuitId) => ({
|
|
23
|
+
condition: { circuitId },
|
|
24
|
+
proof: "",
|
|
25
|
+
inputs: [],
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Create a configured facilitator client with proof generation capabilities.
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* const facilitator = facilitator(config);
|
|
32
|
+
*
|
|
33
|
+
* // Generate disclosure with payment proof
|
|
34
|
+
* const disclosure = await facilitator.generateDisclosure(txHash);
|
|
35
|
+
*
|
|
36
|
+
* // Query Lemma API
|
|
37
|
+
* const results = await lemmaApi.query({ disclosure });
|
|
38
|
+
*/
|
|
39
|
+
const facilitator = (config) => {
|
|
40
|
+
const lemmaClient = createLemmaClient(config);
|
|
41
|
+
const proofGenerator = createProofGenerator(config, lemmaClient);
|
|
42
|
+
return {
|
|
43
|
+
/**
|
|
44
|
+
* Generate a disclosure with payment proof for a transaction.
|
|
45
|
+
*/
|
|
46
|
+
generateDisclosure: async (txHash) => {
|
|
47
|
+
const { proof } = await proofGenerator.generateProof(txHash);
|
|
48
|
+
return disclosure(proof);
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Get an empty disclosure template with condition for the configured circuit.
|
|
52
|
+
*/
|
|
53
|
+
getEmptyDisclosure: (_) => emptyDisclosure(config.circuitId),
|
|
54
|
+
/**
|
|
55
|
+
* Get the configuration.
|
|
56
|
+
*/
|
|
57
|
+
getConfig: (_) => config,
|
|
58
|
+
/**
|
|
59
|
+
* Get the Lemma client.
|
|
60
|
+
*/
|
|
61
|
+
getLemmaClient: (_) => lemmaClient,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Default x402 facilitator configuration for Base Sepolia.
|
|
66
|
+
*/
|
|
67
|
+
const defaultConfig = (overrides = {}) => R.mergeRight({
|
|
68
|
+
payToAddress: "0x",
|
|
69
|
+
network: "eip155:84532",
|
|
70
|
+
price: "$0.001",
|
|
71
|
+
ethereumRpcUrl: "https://sepolia.base.org",
|
|
72
|
+
chainId: 84532,
|
|
73
|
+
lemmaApiBase: "https://workers.lemma.workers.dev",
|
|
74
|
+
circuitId: "x402-payment-v1",
|
|
75
|
+
minAmount: 1000n, // $0.001 USDC in smallest units (6 decimals)
|
|
76
|
+
requiredConfirmations: 6,
|
|
77
|
+
maxProofAge: 3600, // 1 hour
|
|
78
|
+
ipfsGateway: "https://ipfs.io/ipfs/",
|
|
79
|
+
}, overrides);
|
|
80
|
+
export { facilitator, createProofGenerator, disclosure, emptyDisclosure, defaultConfig,
|
|
81
|
+
// createLemmaClient is not exported
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoB,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,CAAC,MAAM,OAAO,CAAC;AAE3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D;;GAEG;AACH,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAe,EAAE,CACxD,MAAM,CAAC;IACL,OAAO,EAAE,MAAM,CAAC,YAAY;IAC5B,MAAM,EAAE,MAAM,CAAC,WAAW;CAC3B,CAAC,CAAC;AAEL;;GAEG;AACH,MAAM,UAAU,GAAG,CAAC,KAAY,EAAc,EAAE,CAC9C,CAAC;IACC,SAAS,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE;IACzC,KAAK,EAAE,KAAK,CAAC,KAAK;IAClB,MAAM,EAAE,KAAK,CAAC,MAAM;CACrB,CAAC,CAAC;AAEL;;GAEG;AACH,MAAM,eAAe,GAAG,CAAC,SAAiB,EAAc,EAAE,CACxD,CAAC;IACC,SAAS,EAAE,EAAE,SAAS,EAAE;IACxB,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,EAAE;CACX,CAAC,CAAC;AAEL;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,EAAE;IACrC,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,cAAc,GAAG,oBAAoB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAEjE,OAAO;QACL;;WAEG;QACH,kBAAkB,EAAE,KAAK,EAAE,MAAqB,EAAuB,EAAE;YACvE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YAE7D,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QAED;;WAEG;QACH,kBAAkB,EAAE,CAAC,CAAa,EAAc,EAAE,CAChD,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC;QAEnC;;WAEG;QACH,SAAS,EAAE,CAAC,CAAa,EAAU,EAAE,CAAC,MAAM;QAE5C;;WAEG;QACH,cAAc,EAAE,CAAC,CAAa,EAAe,EAAE,CAAC,WAAW;KAC5D,CAAC;AACJ,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,aAAa,GAAG,CAAC,YAA6B,EAAE,EAAU,EAAE,CAChE,CAAC,CAAC,UAAU,CACV;IACE,YAAY,EAAE,IAAqB;IACnC,OAAO,EAAE,cAAc;IACvB,KAAK,EAAE,QAAQ;IACf,cAAc,EAAE,0BAA0B;IAC1C,OAAO,EAAE,KAAK;IACd,YAAY,EAAE,mCAAmC;IACjD,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,KAAK,EAAE,6CAA6C;IAC/D,qBAAqB,EAAE,CAAC;IACxB,WAAW,EAAE,IAAI,EAAE,SAAS;IAC5B,WAAW,EAAE,uBAAuB;CACrC,EACD,SAAS,CACV,CAAC;AAEJ,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,UAAU,EACV,eAAe,EACf,aAAa;AACb,oCAAoC;EACrC,CAAC"}
|