@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.
Files changed (126) hide show
  1. package/broadcast/DeployPaymentVerifier.s.sol/10143/run-1775996174982.json +34 -0
  2. package/broadcast/DeployPaymentVerifier.s.sol/10143/run-1775996423983.json +51 -0
  3. package/broadcast/DeployPaymentVerifier.s.sol/10143/run-1775996907850.json +51 -0
  4. package/broadcast/DeployPaymentVerifier.s.sol/10143/run-latest.json +51 -0
  5. package/cache/DeployPaymentVerifier.s.sol/10143/run-1775996174982.json +7 -0
  6. package/cache/DeployPaymentVerifier.s.sol/10143/run-1775996423983.json +7 -0
  7. package/cache/DeployPaymentVerifier.s.sol/10143/run-1775996907850.json +7 -0
  8. package/cache/DeployPaymentVerifier.s.sol/10143/run-latest.json +7 -0
  9. package/cache/solidity-files-cache.json +1 -0
  10. package/circuits/README.md +78 -0
  11. package/circuits/payment-comprehensive.test.mjs +272 -0
  12. package/circuits/payment.circom +89 -0
  13. package/circuits/payment.test.mjs +173 -0
  14. package/dist/index.d.ts +48 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +83 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/proof-generator.d.ts +26 -0
  19. package/dist/proof-generator.d.ts.map +1 -0
  20. package/dist/proof-generator.js +154 -0
  21. package/dist/proof-generator.js.map +1 -0
  22. package/dist/proof-generator.test.d.ts +7 -0
  23. package/dist/proof-generator.test.d.ts.map +1 -0
  24. package/dist/proof-generator.test.js +121 -0
  25. package/dist/proof-generator.test.js.map +1 -0
  26. package/dist/transaction-watcher.d.ts +42 -0
  27. package/dist/transaction-watcher.d.ts.map +1 -0
  28. package/dist/transaction-watcher.js +88 -0
  29. package/dist/transaction-watcher.js.map +1 -0
  30. package/dist/transaction-watcher.test.d.ts +7 -0
  31. package/dist/transaction-watcher.test.d.ts.map +1 -0
  32. package/dist/transaction-watcher.test.js +111 -0
  33. package/dist/transaction-watcher.test.js.map +1 -0
  34. package/dist/types.d.ts +76 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/foundry.lock +11 -0
  39. package/foundry.toml +16 -0
  40. package/lib/forge-std/.gitattributes +1 -0
  41. package/lib/forge-std/.github/CODEOWNERS +1 -0
  42. package/lib/forge-std/.github/dependabot.yml +6 -0
  43. package/lib/forge-std/.github/workflows/ci.yml +161 -0
  44. package/lib/forge-std/.github/workflows/sync.yml +36 -0
  45. package/lib/forge-std/CONTRIBUTING.md +193 -0
  46. package/lib/forge-std/LICENSE-APACHE +203 -0
  47. package/lib/forge-std/LICENSE-MIT +25 -0
  48. package/lib/forge-std/README.md +314 -0
  49. package/lib/forge-std/RELEASE_CHECKLIST.md +12 -0
  50. package/lib/forge-std/foundry.toml +18 -0
  51. package/lib/forge-std/package.json +16 -0
  52. package/lib/forge-std/scripts/vm.py +636 -0
  53. package/lib/forge-std/src/Base.sol +48 -0
  54. package/lib/forge-std/src/Config.sol +60 -0
  55. package/lib/forge-std/src/LibVariable.sol +477 -0
  56. package/lib/forge-std/src/Script.sol +28 -0
  57. package/lib/forge-std/src/StdAssertions.sol +1300 -0
  58. package/lib/forge-std/src/StdChains.sol +303 -0
  59. package/lib/forge-std/src/StdCheats.sol +825 -0
  60. package/lib/forge-std/src/StdConfig.sol +632 -0
  61. package/lib/forge-std/src/StdConstants.sol +30 -0
  62. package/lib/forge-std/src/StdError.sol +15 -0
  63. package/lib/forge-std/src/StdInvariant.sol +161 -0
  64. package/lib/forge-std/src/StdJson.sol +275 -0
  65. package/lib/forge-std/src/StdMath.sol +47 -0
  66. package/lib/forge-std/src/StdStorage.sol +476 -0
  67. package/lib/forge-std/src/StdStyle.sol +333 -0
  68. package/lib/forge-std/src/StdToml.sol +275 -0
  69. package/lib/forge-std/src/StdUtils.sol +200 -0
  70. package/lib/forge-std/src/Test.sol +32 -0
  71. package/lib/forge-std/src/Vm.sol +2533 -0
  72. package/lib/forge-std/src/console.sol +1551 -0
  73. package/lib/forge-std/src/console2.sol +4 -0
  74. package/lib/forge-std/src/interfaces/IERC1155.sol +105 -0
  75. package/lib/forge-std/src/interfaces/IERC165.sol +12 -0
  76. package/lib/forge-std/src/interfaces/IERC20.sol +43 -0
  77. package/lib/forge-std/src/interfaces/IERC4626.sol +190 -0
  78. package/lib/forge-std/src/interfaces/IERC6909.sol +72 -0
  79. package/lib/forge-std/src/interfaces/IERC721.sol +164 -0
  80. package/lib/forge-std/src/interfaces/IERC7540.sol +145 -0
  81. package/lib/forge-std/src/interfaces/IERC7575.sol +241 -0
  82. package/lib/forge-std/src/interfaces/IMulticall3.sol +68 -0
  83. package/lib/forge-std/src/safeconsole.sol +13248 -0
  84. package/lib/forge-std/test/CommonBase.t.sol +44 -0
  85. package/lib/forge-std/test/Config.t.sol +381 -0
  86. package/lib/forge-std/test/LibVariable.t.sol +452 -0
  87. package/lib/forge-std/test/StdAssertions.t.sol +141 -0
  88. package/lib/forge-std/test/StdChains.t.sol +227 -0
  89. package/lib/forge-std/test/StdCheats.t.sol +638 -0
  90. package/lib/forge-std/test/StdConstants.t.sol +38 -0
  91. package/lib/forge-std/test/StdError.t.sol +119 -0
  92. package/lib/forge-std/test/StdJson.t.sol +49 -0
  93. package/lib/forge-std/test/StdMath.t.sol +202 -0
  94. package/lib/forge-std/test/StdStorage.t.sol +532 -0
  95. package/lib/forge-std/test/StdStyle.t.sol +110 -0
  96. package/lib/forge-std/test/StdToml.t.sol +49 -0
  97. package/lib/forge-std/test/StdUtils.t.sol +342 -0
  98. package/lib/forge-std/test/Vm.t.sol +18 -0
  99. package/lib/forge-std/test/compilation/CompilationScript.sol +8 -0
  100. package/lib/forge-std/test/compilation/CompilationScriptBase.sol +8 -0
  101. package/lib/forge-std/test/compilation/CompilationTest.sol +8 -0
  102. package/lib/forge-std/test/compilation/CompilationTestBase.sol +8 -0
  103. package/lib/forge-std/test/fixtures/broadcast.log.json +187 -0
  104. package/lib/forge-std/test/fixtures/config.toml +81 -0
  105. package/lib/forge-std/test/fixtures/test.json +8 -0
  106. package/lib/forge-std/test/fixtures/test.toml +6 -0
  107. package/package.json +55 -0
  108. package/package.json.backup +55 -0
  109. package/scripts/DeployPaymentVerifier.s.sol +18 -0
  110. package/scripts/build.sh +61 -0
  111. package/scripts/deploy.sh +65 -0
  112. package/scripts/fix-verifier.mjs +64 -0
  113. package/scripts/register-circuit.ts +213 -0
  114. package/scripts/test-with-circuit.mjs +88 -0
  115. package/src/index.ts +115 -0
  116. package/src/proof-generator.test.ts +139 -0
  117. package/src/proof-generator.ts +220 -0
  118. package/src/transaction-watcher.test.ts +153 -0
  119. package/src/transaction-watcher.ts +154 -0
  120. package/src/types.ts +110 -0
  121. package/tsconfig.json +14 -0
  122. package/tsconfig.tsbuildinfo +1 -0
  123. package/vitest.config.ts +18 -0
  124. package/workers/x402-facilitator/src/index.ts +582 -0
  125. package/workers/x402-facilitator/src/x402-env.d.ts +25 -0
  126. package/workers/x402-facilitator/wrangler.toml +19 -0
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Combined test script that runs both TypeScript tests and circuit tests
4
+ * Usage: node scripts/test-with-circuit.mjs
5
+ */
6
+
7
+ import { spawn } from 'child_process';
8
+ import { existsSync } from 'fs';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, resolve } from 'path';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const ROOT_DIR = resolve(__dirname, '..');
14
+
15
+ // Check if build files exist
16
+ function checkBuildFiles() {
17
+ const requiredFiles = [
18
+ 'build/payment_js/payment.wasm',
19
+ 'build/payment_final.zkey',
20
+ 'build/verification_key.json'
21
+ ];
22
+
23
+ const missingFiles = requiredFiles.filter(file => !existsSync(resolve(ROOT_DIR, file)));
24
+ return missingFiles;
25
+ }
26
+
27
+ // Helper function to run commands
28
+ function runCommand(command, args = [], cwd = ROOT_DIR) {
29
+ return new Promise((resolve, reject) => {
30
+ console.log(`\n>>> Running: ${command} ${args.join(' ')}`);
31
+
32
+ const child = spawn(command, args, {
33
+ cwd,
34
+ stdio: 'inherit',
35
+ shell: true
36
+ });
37
+
38
+ child.on('close', (code) => {
39
+ if (code === 0) {
40
+ resolve();
41
+ } else {
42
+ reject(new Error(`Command failed with exit code ${code}`));
43
+ }
44
+ });
45
+
46
+ child.on('error', (error) => {
47
+ reject(error);
48
+ });
49
+ });
50
+ }
51
+
52
+ async function main() {
53
+ console.log('=== Running X402 Package Tests ===\n');
54
+
55
+ try {
56
+ // 1. Run TypeScript tests (direct vitest execution)
57
+ console.log('1. Running TypeScript tests with Vitest...');
58
+ await runCommand('npx', ['vitest', 'run']);
59
+
60
+ // 2. Check circuit build files
61
+ console.log('\n2. Checking circuit build files...');
62
+ const missingFiles = checkBuildFiles();
63
+
64
+ if (missingFiles.length > 0) {
65
+ console.log('⚠️ Circuit build files missing. Building circuit...');
66
+ await runCommand('npm', ['run', 'build:circuit']);
67
+ } else {
68
+ console.log('✅ Circuit build files are present.');
69
+ }
70
+
71
+ // 3. Run circuit tests
72
+ console.log('\n3. Running circuit tests...');
73
+ await runCommand('node', ['circuits/payment.test.mjs']);
74
+
75
+ console.log('\n✅ All tests passed!');
76
+ process.exit(0);
77
+
78
+ } catch (error) {
79
+ console.error('\n❌ Test failed:', error.message);
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ // Execute main function
85
+ main().catch(error => {
86
+ console.error('Unhandled error:', error);
87
+ process.exit(1);
88
+ });
package/src/index.ts ADDED
@@ -0,0 +1,115 @@
1
+ import { create, type LemmaClient } from "@lemmaoracle/sdk";
2
+ import * as R from "ramda";
3
+ import type { Config, Disclosure, Proof, Transaction, ProofResult } from "./types.js";
4
+ import { createProofGenerator } from "./proof-generator.js";
5
+
6
+ /**
7
+ * Create a Lemma API client from config.
8
+ */
9
+ const createLemmaClient = (config: Config): LemmaClient =>
10
+ create({
11
+ apiBase: config.lemmaApiBase,
12
+ apiKey: config.lemmaApiKey,
13
+ });
14
+
15
+ /**
16
+ * Build a disclosure object from a proof.
17
+ */
18
+ const disclosure = (proof: Proof): Disclosure =>
19
+ ({
20
+ condition: { circuitId: proof.circuitId },
21
+ proof: proof.proof,
22
+ inputs: proof.inputs,
23
+ });
24
+
25
+ /**
26
+ * Build disclosure with empty proof for initial query (will be filled by facilitator).
27
+ */
28
+ const emptyDisclosure = (circuitId: string): Disclosure =>
29
+ ({
30
+ condition: { circuitId },
31
+ proof: "",
32
+ inputs: [],
33
+ });
34
+
35
+ /**
36
+ * Create a configured facilitator client with proof generation capabilities.
37
+ *
38
+ * Usage:
39
+ * const facilitator = facilitator(config);
40
+ *
41
+ * // Generate disclosure with payment proof
42
+ * const disclosure = await facilitator.generateDisclosure(txHash);
43
+ *
44
+ * // Query Lemma API
45
+ * const results = await lemmaApi.query({ disclosure });
46
+ */
47
+ const facilitator = (config: Config) => {
48
+ const lemmaClient = createLemmaClient(config);
49
+ const proofGenerator = createProofGenerator(config, lemmaClient);
50
+
51
+ return {
52
+ /**
53
+ * Generate a disclosure with payment proof for a transaction.
54
+ */
55
+ generateDisclosure: async (txHash: `0x${string}`): Promise<Disclosure> => {
56
+ const { proof } = await proofGenerator.generateProof(txHash);
57
+
58
+ return disclosure(proof);
59
+ },
60
+
61
+ /**
62
+ * Get an empty disclosure template with condition for the configured circuit.
63
+ */
64
+ getEmptyDisclosure: (_?: undefined): Disclosure =>
65
+ emptyDisclosure(config.circuitId),
66
+
67
+ /**
68
+ * Get the configuration.
69
+ */
70
+ getConfig: (_?: undefined): Config => config,
71
+
72
+ /**
73
+ * Get the Lemma client.
74
+ */
75
+ getLemmaClient: (_?: undefined): LemmaClient => lemmaClient,
76
+ };
77
+ };
78
+
79
+ /**
80
+ * Default x402 facilitator configuration for Base Sepolia.
81
+ */
82
+ const defaultConfig = (overrides: Partial<Config> = {}): Config =>
83
+ R.mergeRight(
84
+ {
85
+ payToAddress: "0x" as `0x${string}`,
86
+ network: "eip155:84532",
87
+ price: "$0.001",
88
+ ethereumRpcUrl: "https://sepolia.base.org",
89
+ chainId: 84532,
90
+ lemmaApiBase: "https://workers.lemma.workers.dev",
91
+ circuitId: "x402-payment-v1",
92
+ minAmount: 1000n, // $0.001 USDC in smallest units (6 decimals)
93
+ requiredConfirmations: 6,
94
+ maxProofAge: 3600, // 1 hour
95
+ ipfsGateway: "https://ipfs.io/ipfs/",
96
+ },
97
+ overrides,
98
+ );
99
+
100
+ export {
101
+ facilitator,
102
+ createProofGenerator,
103
+ disclosure,
104
+ emptyDisclosure,
105
+ defaultConfig,
106
+ // createLemmaClient is not exported
107
+ };
108
+
109
+ export type {
110
+ Config,
111
+ Disclosure,
112
+ Proof,
113
+ Transaction,
114
+ ProofResult,
115
+ };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Tests for x402 payment proof generator.
3
+ *
4
+ * Test files are exempt from FP rules.
5
+ */
6
+
7
+ import { describe, it, expect, vi, beforeEach } from "vitest";
8
+ import { createProofGenerator } from "./proof-generator.js";
9
+ import type { Config } from "./types.js";
10
+
11
+ // Mock viem
12
+ vi.mock("viem", async () => {
13
+ const actual = await vi.importActual("viem");
14
+ return {
15
+ ...actual,
16
+ createPublicClient: vi.fn(() => ({
17
+ getTransaction: vi.fn().mockResolvedValue({
18
+ hash: "0xabc123",
19
+ from: "0xsender",
20
+ to: "0x2222222222222222222222222222222222222222",
21
+ value: 1000n,
22
+ }),
23
+ getTransactionReceipt: vi.fn().mockResolvedValue({
24
+ status: "success",
25
+ blockNumber: 1000n,
26
+ gasUsed: 21000n,
27
+ effectiveGasPrice: 1000000000n,
28
+ confirmations: 6,
29
+ }),
30
+ getBlock: vi.fn().mockResolvedValue({
31
+ number: 1006n,
32
+ timestamp: 1712000000n,
33
+ }),
34
+ })),
35
+ };
36
+ });
37
+
38
+ // Mock Lemma SDK
39
+ vi.mock("@lemmaoracle/sdk", async () => {
40
+ const actual = await vi.importActual("@lemmaoracle/sdk");
41
+ return {
42
+ ...actual,
43
+ prover: {
44
+ prove: vi.fn().mockResolvedValue({
45
+ proof: "mock-proof-base64",
46
+ publicInputs: ["input1", "input2"],
47
+ }),
48
+ },
49
+ createClient: vi.fn().mockReturnValue({
50
+ apiBase: "https://workers.lemma.workers.dev",
51
+ }),
52
+ };
53
+ });
54
+
55
+ const mockConfig: Config = {
56
+ payToAddress: "0x2222222222222222222222222222222222222222" as `0x${string}`,
57
+ network: "eip155:84532",
58
+ price: "$0.001",
59
+ ethereumRpcUrl: "https://sepolia.base.org",
60
+ chainId: 84532,
61
+ lemmaApiBase: "https://workers.lemma.workers.dev",
62
+ circuitId: "x402-payment-v1",
63
+ minAmount: 1000n,
64
+ requiredConfirmations: 6,
65
+ maxProofAge: 3600,
66
+ ipfsGateway: "https://ipfs.io/ipfs/",
67
+ };
68
+
69
+ const mockLemmaClient = {
70
+ apiBase: "https://workers.lemma.workers.dev",
71
+ };
72
+
73
+ describe("X402 Proof Generator", () => {
74
+ beforeEach(() => {
75
+ vi.clearAllMocks();
76
+ });
77
+
78
+ it("should create a proof generator", () => {
79
+ const generator = createProofGenerator(mockConfig, mockLemmaClient as any);
80
+
81
+ expect(generator).toBeDefined();
82
+ expect(typeof generator.generateProof).toBe("function");
83
+ expect(typeof generator.toFieldElements).toBe("function");
84
+ });
85
+
86
+ it("should convert payment details to field elements", async () => {
87
+ const generator = createProofGenerator(mockConfig, mockLemmaClient as any);
88
+
89
+ const mockPayment = {
90
+ txHash: "0xabc123" as `0x${string}`,
91
+ from: "0x1111111111111111111111111111111111111111" as `0x${string}`,
92
+ to: "0x2222222222222222222222222222222222222222" as `0x${string}`,
93
+ amount: 1000n,
94
+ timestamp: 1712000000,
95
+ blockNumber: 1000n,
96
+ confirmations: 6,
97
+ };
98
+
99
+ const result = await generator.toFieldElements(mockPayment, mockConfig);
100
+
101
+ expect(result).toBeDefined();
102
+ expect(result.witness).toBeDefined();
103
+ expect(result.publicInputs).toBeDefined();
104
+ expect(result.witness.amount).toBeDefined();
105
+ expect(result.witness.minAmount).toBeDefined();
106
+ });
107
+
108
+ it("should handle empty txHash gracefully", async () => {
109
+ const generator = createProofGenerator(mockConfig, mockLemmaClient as any);
110
+
111
+ expect(generator.generateProof).toBeDefined();
112
+ });
113
+ });
114
+
115
+ describe("Field Element Conversion", () => {
116
+ it("should convert addresses to field elements", async () => {
117
+ const generator = createProofGenerator(mockConfig, mockLemmaClient as any);
118
+
119
+ const mockPayment = {
120
+ txHash: "0xabc123" as `0x${string}`,
121
+ from: "0x1111111111111111111111111111111111111111" as `0x${string}`,
122
+ to: "0x2222222222222222222222222222222222222222" as `0x${string}`,
123
+ amount: 1000n,
124
+ timestamp: 1712000000,
125
+ blockNumber: 1000n,
126
+ confirmations: 6,
127
+ };
128
+
129
+ const { witness } = await generator.toFieldElements(mockPayment, mockConfig);
130
+
131
+ // Verify field elements are present and not null
132
+ expect(witness.txHashPacked_0).toBeDefined();
133
+ expect(witness.txHashPacked_1).toBeDefined();
134
+ expect(witness.recipientLow).toBeDefined();
135
+ expect(witness.amount).toBeDefined();
136
+ expect(witness.timestamp).toBeDefined();
137
+ expect(witness.minAmount).toBeDefined();
138
+ });
139
+ });
@@ -0,0 +1,220 @@
1
+ /**
2
+ * X402 Payment Proof Generator
3
+ *
4
+ * Generates ZK proofs for x402 payment transactions so that
5
+ * Lemma's disclosure.condition API can verify payments without
6
+ * exposing transaction details.
7
+ */
8
+
9
+ import { createPublicClient, http, type PublicClient } from "viem";
10
+ import { prover, type LemmaClient } from "@lemmaoracle/sdk";
11
+ import * as R from "ramda";
12
+ import type {
13
+ ProofResult,
14
+ Config,
15
+ Transaction,
16
+ Proof,
17
+ } from "./types.js";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Constants
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** Default IPFS gateway if not specified in config */
24
+ const _DEFAULT_IPFS_GATEWAY = "https://ipfs.io/ipfs/";
25
+
26
+ /** Poseidon hash function for field element hashing */
27
+ const toFieldElement = (value: string): string =>
28
+ BigInt(value) < 0n
29
+ ? String(BigInt(value) % 21888242871839275222246405745257275088548364400416034343698204186575808495617n)
30
+ : value;
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Proof Generator Class
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * Generates x402 payment proofs using Lemma's prover infrastructure.
38
+ *
39
+ * Usage:
40
+ * const generator = new X402ProofGenerator(config, lemmaClient);
41
+ * const result = await generator.generateProof(txHash);
42
+ */
43
+ const createProofGenerator = (
44
+ config: Config,
45
+ lemmaClient: LemmaClient,
46
+ ): Readonly<{
47
+ generateProof: (txHash: `0x${string}`) => Promise<ProofResult>;
48
+ toFieldElements: (
49
+ payment: Transaction,
50
+ config: Config,
51
+ ) => Readonly<{ witness: Record<string, string>; publicInputs: string[] }>;
52
+ }> => {
53
+ /** Create public client for transaction inspection */
54
+ const publicClient: PublicClient = createPublicClient({
55
+ transport: http(config.ethereumRpcUrl),
56
+ });
57
+
58
+ return {
59
+ /**
60
+ * Generate a payment proof for a given transaction hash.
61
+ * This fetches the transaction details, computes the witness,
62
+ * and generates the ZK proof.
63
+ */
64
+ generateProof: async (txHash: `0x${string}`): Promise<ProofResult> => {
65
+ // Step 1: Fetch transaction details from blockchain
66
+ const payment = await fetchPaymentTransaction(txHash, publicClient);
67
+
68
+ // Step 2: Generate proof
69
+ const proofResult = await generateZKProof(
70
+ lemmaClient,
71
+ payment,
72
+ config,
73
+ );
74
+
75
+ return R.always({
76
+ payment,
77
+ proof: proofResult,
78
+ })();
79
+ },
80
+
81
+ /**
82
+ * Convert payment transaction details to circuit field elements.
83
+ */
84
+ toFieldElements: (
85
+ payment: Transaction,
86
+ cfg: Config,
87
+ ) => {
88
+ // Split 256-bit txHash into two 128-bit elements
89
+ const txHashBigInt = BigInt(payment.txHash);
90
+ const upper128 = toFieldElement(String(txHashBigInt >> 128n));
91
+ const lower128 = toFieldElement(String(txHashBigInt & ((1n << 128n) - 1n)));
92
+
93
+ // Split 160-bit recipient address
94
+ const recipientBigInt = BigInt(payment.to);
95
+ const recipientLow = toFieldElement(String(recipientBigInt & ((1n << 128n) - 1n)));
96
+ const recipientHigh = toFieldElement(String(recipientBigInt >> 128n));
97
+
98
+ // Amount and timestamp
99
+ const amount = toFieldElement(String(payment.amount));
100
+ const timestamp = toFieldElement(String(payment.timestamp));
101
+ const minAmount = toFieldElement(String(cfg.minAmount));
102
+
103
+ return R.always({
104
+ witness: {
105
+ txHashPacked_0: lower128,
106
+ txHashPacked_1: upper128,
107
+ recipientLow,
108
+ recipientHigh,
109
+ amount,
110
+ timestamp,
111
+ minAmount,
112
+ },
113
+ publicInputs: [
114
+ // commitment will be computed during proof generation
115
+ toFieldElement(String(cfg.minAmount)), // minAmountPublic
116
+ toFieldElement(String(cfg.maxProofAge)), // timestampMax
117
+ ],
118
+ })();
119
+ },
120
+ };
121
+ };
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Private Helpers
125
+ // ---------------------------------------------------------------------------
126
+
127
+ /**
128
+ * Fetch payment transaction details from the blockchain.
129
+ */
130
+ const fetchPaymentTransaction = async (
131
+ txHash: `0x${string}`,
132
+ client: PublicClient,
133
+ ): Promise<Transaction> => {
134
+ const txPromise = client.getTransaction({ hash: txHash });
135
+ const receiptPromise = client.getTransactionReceipt({ hash: txHash });
136
+ const blockPromise = client.getBlock({ blockTag: "latest" });
137
+
138
+ const [tx, receipt, block] = await Promise.all([
139
+ txPromise,
140
+ receiptPromise,
141
+ blockPromise,
142
+ ]);
143
+
144
+ return receipt.status !== "success"
145
+ ? Promise.reject(new Error(`Transaction ${txHash} failed`))
146
+ : client.getBlock({ blockNumber: receipt.blockNumber }).then(
147
+ (blockData) =>
148
+ R.always({
149
+ txHash,
150
+ from: tx.from,
151
+ to: tx.to ?? ("0x" as `0x${string}`),
152
+ amount: tx.value + receipt.effectiveGasPrice * receipt.gasUsed,
153
+ timestamp: Number(blockData.timestamp),
154
+ blockNumber: receipt.blockNumber,
155
+ confirmations: Number(block.number - receipt.blockNumber),
156
+ })(),
157
+ );
158
+ };
159
+
160
+ /**
161
+ * Generate ZK proof for the payment transaction.
162
+ */
163
+ const generateZKProof = async (
164
+ lemmaClient: LemmaClient,
165
+ payment: Transaction,
166
+ config: Config,
167
+ ): Promise<Proof> => {
168
+ // Build witness from payment transaction
169
+ const builder = createProofGenerator(config, lemmaClient);
170
+ const { witness, publicInputs } = builder.toFieldElements(payment, config);
171
+
172
+ // Generate proof using Lemma SDK
173
+ const proofResult = await prover.prove(lemmaClient, {
174
+ circuitId: config.circuitId,
175
+ witness,
176
+ });
177
+
178
+ // Compute the commitment for public inputs
179
+ const commitment = computeCommitment(witness, publicInputs);
180
+
181
+ return R.always({
182
+ proof: proofResult.proof,
183
+ inputs: [commitment, ...publicInputs],
184
+ circuitId: config.circuitId,
185
+ generatedAt: Date.now(),
186
+ })();
187
+ };
188
+
189
+ /**
190
+ * Compute the commitment hash for the circuit public inputs.
191
+ */
192
+ const computeCommitment = (
193
+ witness: Record<string, string>,
194
+ _publicInputs: string[],
195
+ ): string => {
196
+ // The commitment is computed from:
197
+ // poseidon(txHashPacked[0], txHashPacked[1], recipientLow, amount, timestamp, minAmount)
198
+ // This should match the public commitment used in the circuit
199
+ const values = [
200
+ witness.txHashPacked_0,
201
+ witness.txHashPacked_1,
202
+ witness.recipientLow,
203
+ witness.amount,
204
+ witness.timestamp,
205
+ witness.minAmount,
206
+ ];
207
+
208
+ // In a real implementation, use poseidon from circomlib
209
+ // For now, we use a simple hash placeholder
210
+ return values.join(",");
211
+ };
212
+
213
+ export type {
214
+ ProofResult,
215
+ Config,
216
+ Proof,
217
+ Transaction,
218
+ };
219
+
220
+ export { createProofGenerator };