@unionlabs/payments 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2451 @@
1
+ 'use strict';
2
+
3
+ var V = require('viem');
4
+ var accounts = require('viem/accounts');
5
+ var connect = require('@connectrpc/connect');
6
+ var connectWeb = require('@connectrpc/connect-web');
7
+ var protobuf = require('@bufbuild/protobuf');
8
+ var codegenv2 = require('@bufbuild/protobuf/codegenv2');
9
+ var effect = require('effect');
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var V__namespace = /*#__PURE__*/_interopNamespace(V);
30
+
31
+ var __defProp = Object.defineProperty;
32
+ var __export = (target, all) => {
33
+ for (var name in all)
34
+ __defProp(target, name, { get: all[name], enumerable: true });
35
+ };
36
+
37
+ // src/client.ts
38
+ exports.Client = {};
39
+ __export(exports.Client, {
40
+ UnionPrivatePayments: () => exports.UnionPrivatePayments,
41
+ make: () => make,
42
+ waitForBlockCondition: () => exports.waitForBlockCondition
43
+ });
44
+
45
+ // src/constants/z-asset-registry.ts
46
+ var Z_ASSET_REGISTRY = {
47
+ "8453": {
48
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913": "0xF0000101561619d8A61ABd045F47Af4f41Afe62D"
49
+ }
50
+ };
51
+
52
+ // src/poseidon2.ts
53
+ var PRIME = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n;
54
+ var NULLIFIER_DOMAIN = 0xdeadn;
55
+ var ADDRESS_DOMAIN = 0xdeafn;
56
+ var RC_EXT_0 = [
57
+ [
58
+ 0x1da4d6adfb0d0b494584f763db50a81908580a5f5e295e168b9b8d31770fac4fn,
59
+ 0x0946129a2e33b4e819707a56a3b3790eab80d0a0c7a0c63451fab2a59acc074cn
60
+ ],
61
+ [
62
+ 0x2a39b9d5376afd35580abd6952986570867b87303b07140268794dad4b8f82ean,
63
+ 0x27605717d1245c20c546b3c7014e5fa3e4a70e66ecd6de5d32448eea5e3069b2n
64
+ ],
65
+ [
66
+ 0x24c896cb2594e17b973653193a470ed7e771ebb09a228d638e50809c03773632n,
67
+ 0x0911096c45dd9cda0d61d783957003db6d8701c7869a919ad8042e1b7d597a49n
68
+ ]
69
+ ];
70
+ var RC_INT = [
71
+ 0x26ff6166f1e4b99e27eee6d352a8ce26f1daba01aad28021f11f38a70603afdcn,
72
+ // 3
73
+ 0x008e2faedcf76d08ad6591ff90e50fea79bcb5e18cfb7d954d5b89437bf64b7en,
74
+ // 4
75
+ 0x19c9da2379b598ace3ad4d1872470830f6184a3cec71eeb632f98820eb353d78n,
76
+ // 5
77
+ 0x0f7c4eb15d8b0b62a8f6a090ec9610a2ab3dfcdb57e2539aa459a40583dfe96bn,
78
+ // 6
79
+ 0x18b99417dc26b5e079750eba282362d1d46900b47dd5ff809171588b08ac3983n,
80
+ // 7
81
+ 0x1ee044081160b3eee2d4493feab82141c73f1c054b76320a8848af08a8d91a26n,
82
+ // 8
83
+ 0x29bb95c8763efd3e0e87f5df12ee8a150455b6d7a14780d19122220366c258dcn,
84
+ // 9
85
+ 0x22c23eec9cb13ff8a3ee9a363d740653215e8991f7f9ec12067b4705e9a5c9fbn,
86
+ // 10
87
+ 0x23589e033a31a667680c8b18926c3be09115c7644c4f905cc7deefc1690b42dcn,
88
+ // 11
89
+ 0x304e99b887f2e1e92c9c0cde5f2bdd4764f60b98a219f1f0dd64ec938a6a247cn,
90
+ // 12
91
+ 0x22e817865236ad3a76fbe88bbdf31fcae792f326271d53a3a493b2de7f7d8b4cn,
92
+ // 13
93
+ 0x10c9efe573e86fa5b238a3f5c70a00bf02bc7dcfcc4878e026b77c881dc8b1c9n,
94
+ // 14
95
+ 0x0a94f16be920d85f4d6f80e745c6bddbc084f309edffef023ea283c72b89bbb6n,
96
+ // 15
97
+ 0x23ed72b4d01d14e3c7888fedb770494abc2c1ea81d6465b5df3da0ebcd69101an,
98
+ // 16
99
+ 0x17c5115640e4cebeed0e6cbb511ac38498df815a77cb162de8d8f1022eb6bb74n,
100
+ // 17
101
+ 0x2e507fcca290d0d9cf765245750eb04773e09e1fc813959fb99680a82772e4fcn,
102
+ // 18
103
+ 0x0d4a98999f5b39176af6cce65c8d12162433f055cb71d70dfc8803013292bbbfn,
104
+ // 19
105
+ 0x238d8022cc09c21ab3c01261a03dc125321d829e22a7a3b7a1bd3c335eccfa21n,
106
+ // 20
107
+ 0x010cd8e4c2b7051cb81dc8292e35d8e943ce365613d5b39770e454ed9f4ae165n,
108
+ // 21
109
+ 0x088027e54f2a3604b11178cf0ea3c6aa861173a90fb231238e03539590ecc027n,
110
+ // 22
111
+ 0x1b840f5311a2b1d4b4cd7aa7e5a9a6d161165468daa938108302b73e464819dbn,
112
+ // 23
113
+ 0x2bf51a5da1828a1cf9b764b1e16c15929a3a346e44198ea0cb366fcd8db78dc1n,
114
+ // 24
115
+ 0x206ad089d8d296ffe68a6a86767a7fe726b8872f9c7beef9d56a3a50f6f23827n,
116
+ // 25
117
+ 0x24d19193171494fa1a54e0a99ac16d05eaec4b6d617c7c199fc07ff88eac550cn,
118
+ // 26
119
+ 0x1dd654a2ca9d9f24f33d88246a40dfb32c40662278b9c0b995d9f9fbaf152138n,
120
+ // 27
121
+ 0x0d171025c925f6e259d20ecbd3a601108c85f05b0fe7793b8acf57f3789785e4n,
122
+ // 28
123
+ 0x055bef435a43aec245cd99ccb0f7c8791d9e8cf2b80d95dd98b9908fed877d55n,
124
+ // 29
125
+ 0x10d2ac8c61c8a2e88a2a3f42d9359a14be63d0ad4cfd9f203b75765d0e595f0en,
126
+ // 30
127
+ 0x103479710e70996982a84057ec3ba6b2254d7966ddc41e85296e3d0790dcfa56n,
128
+ // 31
129
+ 0x2a366f0448fda3c05914ffb48c765da8de96f9aa340db1638225f8372921807bn,
130
+ // 32
131
+ 0x16be0fb8ef62da17919b6e0378d00594153bb8899aeb686c04626b63285837a4n,
132
+ // 33
133
+ 0x0417038500e9d06c60abbc7f0d0d24c32dec8a0b2aa5a4d52cfd8c78a15bc370n,
134
+ // 34
135
+ 0x26a6873b43ffd2ccf66ec6f4493ff9b54f4d76480bc486a3e5a0308fdd013812n,
136
+ // 35
137
+ 0x0a3314a838f32630a96251914fe5ad262f3db9b2aa8aa9f7391922d36278c498n,
138
+ // 36
139
+ 0x0fde0c5429a6beb07f462d4821f48f86aeadb46a090b15a044f4b59399027da4n,
140
+ // 37
141
+ 0x0abc2d5049972a6b9b357e4163793b0bb517e1eb535a984df11a1c89cda2c8a9n,
142
+ // 38
143
+ 0x0dab51d6e3ebfa661d21722fb21e55051b427a5f173f7f17735205dbb77c464en,
144
+ // 39
145
+ 0x29c36622598b511d51af6cc37123652fb21be5c2d68fb8efe9b92e70a8c1ae03n,
146
+ // 40
147
+ 0x2c03ec80adac2a33ae846bc0b700d0bcc41c4096e53ac6990d6bbe7ea2fbc85cn,
148
+ // 41
149
+ 0x0918fdbe9cf3a59fbdb4c6852d065105317303116017d893b8b521e3cebe1e0dn,
150
+ // 42
151
+ 0x1f19ec22e69ca33f599dd13cd7e495a8176a79df4f7acf79a9d2135acabe2359n,
152
+ // 43
153
+ 0x1c4b037c8ae85ee1eb32b872eb7f76928c4c76b29ceb001346447b2911080704n,
154
+ // 44
155
+ 0x2b68900ed906616d6c826d0bde341766ba3131e04d420de5af0a69c534efd8dbn,
156
+ // 45
157
+ 0x20ca92aa222fcc69448f8dac653c8daaa180ff6dfb397cef723d4f0c782bc7f0n,
158
+ // 46
159
+ 0x10d22d05bdff6bb375276fc82057db337045a5ab7ac053941f6186289b66b2b6n,
160
+ // 47
161
+ 0x0b1ffdbb529367bb98f32ba45784cb435aa910b4a00636d1e5ca79e88bdd6cd9n,
162
+ // 48
163
+ 0x2da32b38e7984bc2ed757ec705eccf8282c7b4f82e5e515f6f89bcc33022ce9fn,
164
+ // 49
165
+ 0x042593ad87403f6d2674b8b55a886725b87eb33958031e94d292cecc6abed1bbn,
166
+ // 50
167
+ 0x181fa1b4d067783a19d7367bf49b3f01051faedab463a6de9308fbd6e7d419f1n,
168
+ // 51
169
+ 0x15aaa6cc9b7900b15683c95515c26028a8e35b00ed8a815c34927188c60de660n
170
+ // 52
171
+ ];
172
+ var RC_EXT_1 = [
173
+ [
174
+ 0x1bf28a93209084bbbc63234f057254c280b1a636f5a0eced6787320212f75a7an,
175
+ 0x1cdb8c8bee5426f02cd9e229776118f156273b312f805c8e6f8c9d81a620cb6fn
176
+ ],
177
+ [
178
+ 0x08299c0abf196d53162e0facb5f1876f516df2505cc387a0f8ea0e8760d5ca7en,
179
+ 0x221643d205fe82778a7b7b58cb65c4962d76c0072cabd1124117269d7c710b8an
180
+ ],
181
+ [
182
+ 0x2d036a95f81cf49bb7a0143a28c88767f6bd10c3f74b22db487920d43343dbffn,
183
+ 0x08a50897c06aafe6ea414fb1bceca2267cd4a39486729fbc6d5d1bb7a172ebd2n
184
+ ]
185
+ ];
186
+ function addMod(a, b) {
187
+ return ((a + b) % PRIME + PRIME) % PRIME;
188
+ }
189
+ function mulMod(a, b) {
190
+ return (a * b % PRIME + PRIME) % PRIME;
191
+ }
192
+ function sbox(x) {
193
+ const x2 = mulMod(x, x);
194
+ const x4 = mulMod(x2, x2);
195
+ return mulMod(x4, x);
196
+ }
197
+ function matMulExternal(s0, s1) {
198
+ const sum = addMod(s0, s1);
199
+ return [addMod(sum, s0), addMod(sum, s1)];
200
+ }
201
+ function matMulInternal(s0, s1) {
202
+ const sum = addMod(s0, s1);
203
+ return [addMod(s0, sum), addMod(addMod(s1, s1), sum)];
204
+ }
205
+ function permutation(state0, state1) {
206
+ let s0 = state0;
207
+ let s1 = state1;
208
+ [s0, s1] = matMulExternal(s0, s1);
209
+ for (let i = 0; i < 3; i++) {
210
+ s0 = addMod(s0, RC_EXT_0[i][0]);
211
+ s1 = addMod(s1, RC_EXT_0[i][1]);
212
+ s0 = sbox(s0);
213
+ s1 = sbox(s1);
214
+ [s0, s1] = matMulExternal(s0, s1);
215
+ }
216
+ for (let i = 0; i < 50; i++) {
217
+ s0 = addMod(s0, RC_INT[i]);
218
+ s0 = sbox(s0);
219
+ [s0, s1] = matMulInternal(s0, s1);
220
+ }
221
+ for (let i = 0; i < 3; i++) {
222
+ s0 = addMod(s0, RC_EXT_1[i][0]);
223
+ s1 = addMod(s1, RC_EXT_1[i][1]);
224
+ s0 = sbox(s0);
225
+ s1 = sbox(s1);
226
+ [s0, s1] = matMulExternal(s0, s1);
227
+ }
228
+ return [s0, s1];
229
+ }
230
+ function poseidon2Hash(inputs) {
231
+ let hashState = 0n;
232
+ for (const block of inputs) {
233
+ const [, permutedState1] = permutation(hashState, block);
234
+ hashState = addMod(block, permutedState1);
235
+ }
236
+ return hashState;
237
+ }
238
+ function hexToBigInt(hex) {
239
+ return BigInt(hex);
240
+ }
241
+ function validateSecret(secret) {
242
+ if (secret >= PRIME) {
243
+ throw new Error(
244
+ `Secret must be less than the BN254 scalar field prime (${PRIME.toString(16)})`
245
+ );
246
+ }
247
+ if (secret < 0n) {
248
+ throw new Error("Secret must be non-negative");
249
+ }
250
+ }
251
+ function addressToBigInt(address) {
252
+ return BigInt(address);
253
+ }
254
+ function computeNullifier(secret, dstChainId) {
255
+ const secretBigInt = hexToBigInt(secret);
256
+ validateSecret(secretBigInt);
257
+ return poseidon2Hash([NULLIFIER_DOMAIN, dstChainId, secretBigInt]);
258
+ }
259
+ function generatePaymentKey() {
260
+ while (true) {
261
+ const bytes = new Uint8Array(32);
262
+ crypto.getRandomValues(bytes);
263
+ const value = BigInt(
264
+ "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")
265
+ );
266
+ if (value < PRIME) {
267
+ return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
268
+ }
269
+ }
270
+ }
271
+ function computeUnspendableAddress(secret, dstChainId, beneficiaries) {
272
+ const secretBigInt = hexToBigInt(secret);
273
+ validateSecret(secretBigInt);
274
+ const hash = poseidon2Hash([
275
+ ADDRESS_DOMAIN,
276
+ dstChainId,
277
+ secretBigInt,
278
+ addressToBigInt(beneficiaries[0]),
279
+ addressToBigInt(beneficiaries[1]),
280
+ addressToBigInt(beneficiaries[2]),
281
+ addressToBigInt(beneficiaries[3])
282
+ ]);
283
+ const addressMask = (1n << 160n) - 1n;
284
+ const addressBigInt = hash & addressMask;
285
+ return `0x${addressBigInt.toString(16).padStart(40, "0")}`;
286
+ }
287
+ var ERC20_ABI = [
288
+ {
289
+ inputs: [{ name: "account", type: "address" }],
290
+ name: "balanceOf",
291
+ outputs: [{ name: "", type: "uint256" }],
292
+ stateMutability: "view",
293
+ type: "function"
294
+ },
295
+ {
296
+ inputs: [],
297
+ name: "decimals",
298
+ outputs: [{ name: "", type: "uint8" }],
299
+ stateMutability: "view",
300
+ type: "function"
301
+ },
302
+ {
303
+ inputs: [],
304
+ name: "symbol",
305
+ outputs: [{ name: "", type: "string" }],
306
+ stateMutability: "view",
307
+ type: "function"
308
+ },
309
+ {
310
+ inputs: [
311
+ { name: "spender", type: "address" },
312
+ { name: "amount", type: "uint256" }
313
+ ],
314
+ name: "approve",
315
+ outputs: [{ name: "", type: "bool" }],
316
+ stateMutability: "nonpayable",
317
+ type: "function"
318
+ },
319
+ {
320
+ inputs: [
321
+ { name: "to", type: "address" },
322
+ { name: "amount", type: "uint256" }
323
+ ],
324
+ name: "transfer",
325
+ outputs: [{ name: "", type: "bool" }],
326
+ stateMutability: "nonpayable",
327
+ type: "function"
328
+ },
329
+ {
330
+ inputs: [
331
+ { name: "from", type: "address" },
332
+ { name: "to", type: "address" },
333
+ { name: "amount", type: "uint256" }
334
+ ],
335
+ name: "transferFrom",
336
+ outputs: [{ name: "", type: "bool" }],
337
+ stateMutability: "nonpayable",
338
+ type: "function"
339
+ }
340
+ ];
341
+ var IBC_STORE_ABI = [
342
+ {
343
+ inputs: [{ name: "clientId", type: "uint32" }],
344
+ name: "getClient",
345
+ outputs: [{ name: "", type: "address" }],
346
+ stateMutability: "view",
347
+ type: "function"
348
+ }
349
+ ];
350
+ var LIGHT_CLIENT_ABI = [
351
+ {
352
+ inputs: [{ name: "clientId", type: "uint32" }],
353
+ name: "getLatestHeight",
354
+ outputs: [{ name: "", type: "uint64" }],
355
+ stateMutability: "view",
356
+ type: "function"
357
+ },
358
+ {
359
+ inputs: [
360
+ { name: "clientId", type: "uint32" },
361
+ { name: "height", type: "uint64" }
362
+ ],
363
+ name: "getConsensusState",
364
+ outputs: [{ name: "", type: "bytes" }],
365
+ stateMutability: "view",
366
+ type: "function"
367
+ }
368
+ ];
369
+ var ZASSET_ABI = [
370
+ {
371
+ inputs: [{ name: "nullifier", type: "uint256" }],
372
+ name: "nullifierBalance",
373
+ outputs: [{ name: "", type: "uint256" }],
374
+ stateMutability: "view",
375
+ type: "function"
376
+ },
377
+ {
378
+ inputs: [{ name: "clientId", type: "uint32" }],
379
+ name: "counterparty",
380
+ outputs: [
381
+ {
382
+ components: [
383
+ { name: "tokenAddressKey", type: "bytes32" },
384
+ { name: "balanceSlot", type: "bytes32" }
385
+ ],
386
+ type: "tuple"
387
+ }
388
+ ],
389
+ stateMutability: "view",
390
+ type: "function"
391
+ },
392
+ {
393
+ inputs: [],
394
+ name: "ibcHandler",
395
+ outputs: [{ name: "", type: "address" }],
396
+ stateMutability: "view",
397
+ type: "function"
398
+ },
399
+ {
400
+ inputs: [{ name: "clientId", type: "uint32" }],
401
+ name: "stateRootIndex",
402
+ outputs: [{ name: "", type: "uint256" }],
403
+ stateMutability: "view",
404
+ type: "function"
405
+ },
406
+ {
407
+ inputs: [],
408
+ name: "underlying",
409
+ outputs: [{ name: "", type: "address" }],
410
+ stateMutability: "view",
411
+ type: "function"
412
+ },
413
+ {
414
+ inputs: [{ name: "amount", type: "uint256" }],
415
+ name: "deposit",
416
+ outputs: [],
417
+ stateMutability: "nonpayable",
418
+ type: "function"
419
+ },
420
+ {
421
+ inputs: [
422
+ { name: "to", type: "address" },
423
+ { name: "amount", type: "uint256" }
424
+ ],
425
+ name: "transfer",
426
+ outputs: [{ name: "", type: "bool" }],
427
+ stateMutability: "nonpayable",
428
+ type: "function"
429
+ },
430
+ {
431
+ inputs: [
432
+ { name: "proof", type: "uint256[8]" },
433
+ { name: "commitments", type: "uint256[2]" },
434
+ { name: "commitmentPok", type: "uint256[2]" },
435
+ {
436
+ name: "lightClients",
437
+ type: "tuple[]",
438
+ components: [
439
+ { name: "clientId", type: "uint32" },
440
+ { name: "height", type: "uint64" }
441
+ ]
442
+ },
443
+ { name: "nullifier", type: "uint256" },
444
+ { name: "value", type: "uint256" },
445
+ { name: "beneficiary", type: "address" },
446
+ { name: "attestedMessage", type: "bytes32" },
447
+ { name: "signature", type: "bytes" }
448
+ ],
449
+ name: "redeem",
450
+ outputs: [],
451
+ stateMutability: "nonpayable",
452
+ type: "function"
453
+ },
454
+ {
455
+ anonymous: false,
456
+ inputs: [
457
+ { indexed: true, name: "from", type: "address" },
458
+ { indexed: true, name: "to", type: "address" },
459
+ { indexed: false, name: "value", type: "uint256" }
460
+ ],
461
+ name: "Transfer",
462
+ type: "event"
463
+ },
464
+ {
465
+ anonymous: false,
466
+ inputs: [
467
+ { indexed: true, name: "nullifier", type: "uint256" },
468
+ { indexed: true, name: "redeemAmount", type: "uint256" },
469
+ { indexed: true, name: "beneficiary", type: "address" }
470
+ ],
471
+ name: "Redeemed",
472
+ type: "event"
473
+ }
474
+ ];
475
+ var IBC_HANDLER_ABI = [
476
+ {
477
+ inputs: [
478
+ {
479
+ components: [
480
+ { name: "clientId", type: "uint32" },
481
+ { name: "clientMessage", type: "bytes" },
482
+ { name: "relayer", type: "address" }
483
+ ],
484
+ name: "msg_",
485
+ type: "tuple"
486
+ }
487
+ ],
488
+ name: "updateClient",
489
+ outputs: [],
490
+ stateMutability: "nonpayable",
491
+ type: "function"
492
+ }
493
+ ];
494
+ exports.RpcClient = class RpcClient {
495
+ client;
496
+ constructor(rpcUrl) {
497
+ this.client = V.createPublicClient({
498
+ transport: V.http(rpcUrl)
499
+ });
500
+ }
501
+ getClient() {
502
+ return this.client;
503
+ }
504
+ async getChainId() {
505
+ return BigInt(await this.client.getChainId());
506
+ }
507
+ async getLatestBlockNumber() {
508
+ return this.client.getBlockNumber();
509
+ }
510
+ async getBalance(tokenAddress, accountAddress, blockNumber) {
511
+ return this.client.readContract({
512
+ address: tokenAddress,
513
+ abi: ERC20_ABI,
514
+ functionName: "balanceOf",
515
+ args: [accountAddress],
516
+ blockNumber
517
+ });
518
+ }
519
+ async getDecimals(tokenAddress) {
520
+ return this.client.readContract({
521
+ address: tokenAddress,
522
+ abi: ERC20_ABI,
523
+ functionName: "decimals"
524
+ });
525
+ }
526
+ async getSymbol(tokenAddress) {
527
+ return this.client.readContract({
528
+ address: tokenAddress,
529
+ abi: ERC20_ABI,
530
+ functionName: "symbol"
531
+ });
532
+ }
533
+ async getLightClientAddress(ibcStoreAddress, clientId) {
534
+ return this.client.readContract({
535
+ address: ibcStoreAddress,
536
+ abi: IBC_STORE_ABI,
537
+ functionName: "getClient",
538
+ args: [clientId]
539
+ });
540
+ }
541
+ async getLatestHeight(lightClientAddress, clientId) {
542
+ return this.client.readContract({
543
+ address: lightClientAddress,
544
+ abi: LIGHT_CLIENT_ABI,
545
+ functionName: "getLatestHeight",
546
+ args: [clientId]
547
+ });
548
+ }
549
+ /**
550
+ * Get consensus state, extracting the state root at the given byte index
551
+ */
552
+ async getConsensusState(lightClientAddress, clientId, height, stateRootIndex) {
553
+ const consensusBytes = await this.client.readContract({
554
+ address: lightClientAddress,
555
+ abi: LIGHT_CLIENT_ABI,
556
+ functionName: "getConsensusState",
557
+ args: [clientId, height]
558
+ });
559
+ const bytes = V.hexToBytes(consensusBytes);
560
+ const idx = Number(stateRootIndex);
561
+ const stateRoot = V.bytesToHex(bytes.slice(idx, idx + 32));
562
+ return { stateRoot };
563
+ }
564
+ async getNullifierBalance(zassetAddress, nullifier) {
565
+ return this.client.readContract({
566
+ address: zassetAddress,
567
+ abi: ZASSET_ABI,
568
+ functionName: "nullifierBalance",
569
+ args: [nullifier]
570
+ });
571
+ }
572
+ async getCounterparty(zassetAddress, clientId) {
573
+ const result = await this.client.readContract({
574
+ address: zassetAddress,
575
+ abi: ZASSET_ABI,
576
+ functionName: "counterparty",
577
+ args: [clientId]
578
+ });
579
+ return {
580
+ tokenAddressKey: result.tokenAddressKey,
581
+ balanceSlot: result.balanceSlot
582
+ };
583
+ }
584
+ async getIbcHandlerAddress(zassetAddress) {
585
+ return this.client.readContract({
586
+ address: zassetAddress,
587
+ abi: ZASSET_ABI,
588
+ functionName: "ibcHandler"
589
+ });
590
+ }
591
+ async getStateRootIndex(zassetAddress, clientId) {
592
+ return this.client.readContract({
593
+ address: zassetAddress,
594
+ abi: ZASSET_ABI,
595
+ functionName: "stateRootIndex",
596
+ args: [clientId]
597
+ });
598
+ }
599
+ async getProof(address, storageKeys, blockNumber) {
600
+ return this.client.getProof({
601
+ address,
602
+ storageKeys,
603
+ blockNumber
604
+ });
605
+ }
606
+ async getBlock(blockNumber) {
607
+ return this.client.getBlock({ blockNumber });
608
+ }
609
+ /**
610
+ * Get redemption history for a nullifier by querying Redeemed events
611
+ */
612
+ async getRedemptionHistory(zassetAddress, nullifier, fromBlock, toBlock) {
613
+ const logs = await this.client.getLogs({
614
+ address: zassetAddress,
615
+ event: {
616
+ type: "event",
617
+ name: "Redeemed",
618
+ inputs: [
619
+ { indexed: true, name: "nullifier", type: "uint256" },
620
+ { indexed: true, name: "redeemAmount", type: "uint256" },
621
+ { indexed: true, name: "beneficiary", type: "address" }
622
+ ]
623
+ },
624
+ args: {
625
+ nullifier
626
+ },
627
+ fromBlock: fromBlock ?? "earliest",
628
+ toBlock: toBlock ?? "latest"
629
+ });
630
+ return logs.map((log) => ({
631
+ txHash: log.transactionHash,
632
+ blockNumber: log.blockNumber,
633
+ redeemAmount: log.args.redeemAmount,
634
+ beneficiary: log.args.beneficiary
635
+ }));
636
+ }
637
+ };
638
+ function computeStorageSlot(address, mappingSlot) {
639
+ const paddedAddress = V.padHex(address, { size: 32 });
640
+ const paddedSlot = V.padHex(V.toHex(mappingSlot), { size: 32 });
641
+ return V.keccak256(V.encodePacked(["bytes32", "bytes32"], [paddedAddress, paddedSlot]));
642
+ }
643
+ async function fetchLightClients(dstClient, zassetAddress, clientIds) {
644
+ const ibcHandlerAddress = await dstClient.getIbcHandlerAddress(zassetAddress);
645
+ const results = [];
646
+ for (const clientId of clientIds) {
647
+ try {
648
+ const lightClientAddress = await dstClient.getLightClientAddress(
649
+ ibcHandlerAddress,
650
+ clientId
651
+ );
652
+ const height = await dstClient.getLatestHeight(lightClientAddress, clientId);
653
+ const stateRootIndex = await dstClient.getStateRootIndex(zassetAddress, clientId);
654
+ const { stateRoot } = await dstClient.getConsensusState(
655
+ lightClientAddress,
656
+ clientId,
657
+ height,
658
+ stateRootIndex
659
+ );
660
+ results.push({ clientId, height, stateRoot });
661
+ } catch {
662
+ continue;
663
+ }
664
+ }
665
+ return results;
666
+ }
667
+ async function fetchMptProof(srcClient, tokenAddress, storageSlot, blockNumber) {
668
+ const proof = await srcClient.getProof(tokenAddress, [storageSlot], blockNumber);
669
+ let storageProof = [];
670
+ let storageValue = "0x0";
671
+ if (proof.storageProof.length > 0 && proof.storageProof[0]) {
672
+ storageProof = proof.storageProof[0].proof;
673
+ storageValue = V.toHex(proof.storageProof[0].value);
674
+ }
675
+ return {
676
+ accountProof: proof.accountProof,
677
+ storageProof,
678
+ storageValue,
679
+ storageRoot: proof.storageHash
680
+ };
681
+ }
682
+ function deterministicShuffleClients(clients, secret) {
683
+ if (clients.length <= 1) {
684
+ return [...clients];
685
+ }
686
+ const secretBytes = V.hexToBytes(secret);
687
+ const heightsBytes = new Uint8Array(clients.length * 8);
688
+ for (let i = 0; i < clients.length; i++) {
689
+ const view = new DataView(heightsBytes.buffer, i * 8, 8);
690
+ view.setBigUint64(0, clients[i].height, false);
691
+ }
692
+ const combined = new Uint8Array(secretBytes.length + heightsBytes.length);
693
+ combined.set(secretBytes, 0);
694
+ combined.set(heightsBytes, secretBytes.length);
695
+ const seedHex = V.sha256(combined);
696
+ const seed = V.hexToBytes(seedHex);
697
+ const shuffled = [...clients];
698
+ let seedIndex = 0;
699
+ for (let i = shuffled.length - 1; i > 0; i--) {
700
+ const randomByte = seed[seedIndex % seed.length];
701
+ seedIndex++;
702
+ const j = randomByte % (i + 1);
703
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
704
+ }
705
+ return shuffled;
706
+ }
707
+ async function signAttestedMessage(message, privateKey) {
708
+ const messageBytes = V.hexToBytes(message);
709
+ if (messageBytes.length !== 32) {
710
+ throw new Error(`Invalid message length: expected 32 bytes, got ${messageBytes.length}`);
711
+ }
712
+ const signature = await accounts.sign({
713
+ hash: message,
714
+ privateKey,
715
+ to: "hex"
716
+ });
717
+ return signature;
718
+ }
719
+ function parseProofJson(proofJsonBytes) {
720
+ const jsonStr = new TextDecoder().decode(proofJsonBytes);
721
+ const raw = JSON.parse(jsonStr);
722
+ const commitments = raw.commitments ?? ["0x0", "0x0"];
723
+ return {
724
+ proof: raw.proof,
725
+ commitments,
726
+ commitmentPok: raw.commitmentPok,
727
+ publicInputs: raw.publicInputs ?? []
728
+ };
729
+ }
730
+ function proofJsonToRedeemParams(proofJson, metadata) {
731
+ const parseBigInt = (s) => {
732
+ if (s.startsWith("0x")) {
733
+ return V.hexToBigInt(s);
734
+ }
735
+ return BigInt(s);
736
+ };
737
+ return {
738
+ proof: proofJson.proof.map(parseBigInt),
739
+ commitments: proofJson.commitments.map(parseBigInt),
740
+ commitmentPok: proofJson.commitmentPok.map(parseBigInt),
741
+ lightClients: metadata.lightClients.map((lc) => ({
742
+ clientId: lc.clientId,
743
+ height: lc.height
744
+ })),
745
+ nullifier: metadata.nullifier,
746
+ value: metadata.value,
747
+ beneficiary: metadata.beneficiary,
748
+ attestedMessage: metadata.attestedMessage,
749
+ signature: metadata.signature
750
+ };
751
+ }
752
+ async function submitRedeem(zassetAddress, params, walletClient) {
753
+ if (!walletClient.account) {
754
+ throw new Error("WalletClient must have an account");
755
+ }
756
+ if (!walletClient.chain) {
757
+ throw new Error("WalletClient must have a chain configured");
758
+ }
759
+ const hash = await walletClient.writeContractSync({
760
+ address: zassetAddress,
761
+ abi: ZASSET_ABI,
762
+ functionName: "redeem",
763
+ args: [
764
+ params.proof,
765
+ params.commitments,
766
+ params.commitmentPok,
767
+ params.lightClients,
768
+ params.nullifier,
769
+ params.value,
770
+ params.beneficiary,
771
+ params.attestedMessage,
772
+ params.signature
773
+ ],
774
+ chain: walletClient.chain,
775
+ account: walletClient.account
776
+ }).then((x) => x.transactionHash);
777
+ return hash;
778
+ }
779
+ function rlpEncodeBlockHeader(block) {
780
+ const toRlpHex = (value) => {
781
+ if (value === void 0 || value === null || value === 0n || value === 0) {
782
+ return "0x";
783
+ }
784
+ let hex = typeof value === "bigint" ? value.toString(16) : value.toString(16);
785
+ if (hex.length % 2 !== 0) {
786
+ hex = "0" + hex;
787
+ }
788
+ return `0x${hex}`;
789
+ };
790
+ const headerFields = [
791
+ block.parentHash,
792
+ block.sha3Uncles,
793
+ block.miner,
794
+ block.stateRoot,
795
+ block.transactionsRoot,
796
+ block.receiptsRoot,
797
+ block.logsBloom ?? "0x" + "00".repeat(256),
798
+ toRlpHex(block.difficulty),
799
+ toRlpHex(block.number),
800
+ toRlpHex(block.gasLimit),
801
+ toRlpHex(block.gasUsed),
802
+ toRlpHex(block.timestamp),
803
+ block.extraData,
804
+ block.mixHash ?? "0x0000000000000000000000000000000000000000000000000000000000000000",
805
+ block.nonce ?? "0x0000000000000000"
806
+ ];
807
+ if (block.baseFeePerGas !== void 0 && block.baseFeePerGas !== null) {
808
+ headerFields.push(toRlpHex(block.baseFeePerGas));
809
+ }
810
+ if (block.withdrawalsRoot) {
811
+ headerFields.push(block.withdrawalsRoot);
812
+ }
813
+ if (block.blobGasUsed !== void 0 && block.blobGasUsed !== null) {
814
+ headerFields.push(toRlpHex(block.blobGasUsed));
815
+ }
816
+ if (block.excessBlobGas !== void 0 && block.excessBlobGas !== null) {
817
+ headerFields.push(toRlpHex(block.excessBlobGas));
818
+ }
819
+ if (block.parentBeaconBlockRoot) {
820
+ headerFields.push(block.parentBeaconBlockRoot);
821
+ }
822
+ const blockAny = block;
823
+ if (blockAny.requestsHash) {
824
+ headerFields.push(blockAny.requestsHash);
825
+ }
826
+ const rlpEncoded = V.toRlp(headerFields);
827
+ const computedHash = V.keccak256(rlpEncoded);
828
+ if (computedHash !== block.hash) {
829
+ throw new Error(
830
+ `RLP encoding mismatch: computed hash ${computedHash} does not match block hash ${block.hash}. Block number: ${block.number}, fields count: ${headerFields.length}`
831
+ );
832
+ }
833
+ return rlpEncoded;
834
+ }
835
+ async function updateLoopbackClient(rpcUrl, ibcHandlerAddress, clientId, height, walletClient) {
836
+ if (!walletClient.account) {
837
+ throw new Error("WalletClient must have an account");
838
+ }
839
+ if (!walletClient.chain) {
840
+ throw new Error("WalletClient must have a chain configured");
841
+ }
842
+ const publicClient = V.createPublicClient({
843
+ transport: V.http(rpcUrl)
844
+ });
845
+ const chainId = await publicClient.getChainId();
846
+ const blockNumber = height === "latest" ? await publicClient.getBlockNumber() : height;
847
+ const block = await publicClient.getBlock({ blockNumber });
848
+ if (!block.number) {
849
+ throw new Error("Block number is null");
850
+ }
851
+ const rlpEncodedHeader = rlpEncodeBlockHeader(block);
852
+ const clientMessage = V.encodeAbiParameters(
853
+ [
854
+ { type: "uint64", name: "height" },
855
+ { type: "bytes", name: "encodedHeader" }
856
+ ],
857
+ [block.number, rlpEncodedHeader]
858
+ );
859
+ let currentBlock = await publicClient.getBlockNumber();
860
+ while (currentBlock <= blockNumber) {
861
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
862
+ currentBlock = await publicClient.getBlockNumber();
863
+ }
864
+ const txHash = await walletClient.writeContractSync({
865
+ address: ibcHandlerAddress,
866
+ abi: IBC_HANDLER_ABI,
867
+ functionName: "updateClient",
868
+ args: [
869
+ {
870
+ clientId,
871
+ clientMessage,
872
+ relayer: walletClient.account.address
873
+ }
874
+ ],
875
+ chain: walletClient.chain,
876
+ account: walletClient.account
877
+ }).then((x) => x.transactionHash);
878
+ return {
879
+ txHash,
880
+ blockNumber: block.number,
881
+ stateRoot: block.stateRoot,
882
+ chainId: BigInt(chainId)
883
+ };
884
+ }
885
+ async function depositToZAsset(rpcUrl, zAssetAddress, depositAddress, amount, walletClient) {
886
+ if (!walletClient.account) {
887
+ throw new Error("WalletClient must have an account");
888
+ }
889
+ if (!walletClient.chain) {
890
+ throw new Error("WalletClient must have a chain configured");
891
+ }
892
+ const publicClient = V.createPublicClient({
893
+ transport: V.http(rpcUrl)
894
+ });
895
+ const chainId = await publicClient.getChainId();
896
+ const underlyingToken = await publicClient.readContract({
897
+ address: zAssetAddress,
898
+ abi: ZASSET_ABI,
899
+ functionName: "underlying"
900
+ });
901
+ if (underlyingToken === "0x0000000000000000000000000000000000000000") {
902
+ throw new Error("ZAsset is not a wrapped token (underlying is zero address)");
903
+ }
904
+ const approveReceipt = await walletClient.writeContractSync({
905
+ address: underlyingToken,
906
+ abi: ERC20_ABI,
907
+ functionName: "approve",
908
+ args: [zAssetAddress, amount],
909
+ chain: walletClient.chain,
910
+ account: walletClient.account
911
+ });
912
+ if (approveReceipt.status === "reverted") {
913
+ throw new Error(`Approve transaction reverted: ${approveReceipt.transactionHash}`);
914
+ }
915
+ const depositReceipt = await walletClient.writeContractSync({
916
+ address: zAssetAddress,
917
+ abi: ZASSET_ABI,
918
+ functionName: "deposit",
919
+ args: [amount],
920
+ chain: walletClient.chain,
921
+ account: walletClient.account
922
+ });
923
+ if (depositReceipt.status === "reverted") {
924
+ throw new Error(`Deposit transaction reverted: ${depositReceipt.transactionHash}`);
925
+ }
926
+ const transferReceipt = await walletClient.writeContractSync({
927
+ address: zAssetAddress,
928
+ abi: ZASSET_ABI,
929
+ functionName: "transfer",
930
+ args: [depositAddress, amount],
931
+ chain: walletClient.chain,
932
+ account: walletClient.account
933
+ });
934
+ if (transferReceipt.status === "reverted") {
935
+ throw new Error(`Transfer transaction reverted: ${transferReceipt.transactionHash}`);
936
+ }
937
+ return {
938
+ txHash: transferReceipt.transactionHash,
939
+ underlyingToken,
940
+ chainId: BigInt(chainId)
941
+ };
942
+ }
943
+ var file_prover = /* @__PURE__ */ codegenv2.fileDesc("Cgxwcm92ZXIucHJvdG8SBnByb3ZlciI0CgxQcm9vZlJlcXVlc3QSJAoHd2l0bmVzcxgBIAEoCzITLnByb3Zlci5XaXRuZXNzRGF0YSK1AgoLV2l0bmVzc0RhdGESDgoGc2VjcmV0GAEgASgJEhQKDGRzdF9jaGFpbl9pZBgCIAEoBBIVCg1iZW5lZmljaWFyaWVzGAMgAygJEhMKC2JlbmVmaWNpYXJ5GAQgASgJEhUKDXJlZGVlbV9hbW91bnQYBSABKAkSGAoQYWxyZWFkeV9yZWRlZW1lZBgGIAEoCRIuCg1saWdodF9jbGllbnRzGAcgAygLMhcucHJvdmVyLkxpZ2h0Q2xpZW50RGF0YRIdChVzZWxlY3RlZF9jbGllbnRfaW5kZXgYCCABKA0SJwoJbXB0X3Byb29mGAkgASgLMhQucHJvdmVyLk1QVFByb29mRGF0YRIVCg10b2tlbl9hZGRyZXNzGAogASgJEhQKDG1hcHBpbmdfc2xvdBgLIAEoCSJICg9MaWdodENsaWVudERhdGESEQoJY2xpZW50X2lkGAEgASgNEg4KBmhlaWdodBgCIAEoBBISCgpzdGF0ZV9yb290GAMgASgJImkKDE1QVFByb29mRGF0YRIVCg1hY2NvdW50X3Byb29mGAEgAygJEhUKDXN0b3JhZ2VfcHJvb2YYAiADKAkSFQoNc3RvcmFnZV92YWx1ZRgDIAEoCRIUCgxzdG9yYWdlX3Jvb3QYBCABKAkifAoMUG9sbFJlc3BvbnNlEiIKB3BlbmRpbmcYASABKAsyDy5wcm92ZXIuUGVuZGluZ0gAEhwKBGRvbmUYAiABKAsyDC5wcm92ZXIuRG9uZUgAEiAKBmZhaWxlZBgDIAEoCzIOLnByb3Zlci5GYWlsZWRIAEIICgZyZXN1bHQiCQoHUGVuZGluZyIuCgREb25lEhIKCnByb29mX2pzb24YASABKAwSEgoKY3JlYXRlZF9hdBgCIAEoAyIfCgZGYWlsZWQSFQoNZXJyb3JfbWVzc2FnZRgBIAEoCSIRCg9WZXJpZmllclJlcXVlc3QiLQoQVmVyaWZpZXJSZXNwb25zZRIZChF2ZXJpZmllcl9jb250cmFjdBgBIAEoCTKIAQoNUHJvdmVyU2VydmljZRIyCgRQb2xsEhQucHJvdmVyLlByb29mUmVxdWVzdBoULnByb3Zlci5Qb2xsUmVzcG9uc2USQwoORXhwb3J0VmVyaWZpZXISFy5wcm92ZXIuVmVyaWZpZXJSZXF1ZXN0GhgucHJvdmVyLlZlcmlmaWVyUmVzcG9uc2VCHFoacHJpdmF0ZS10cmFuc2Zlci9hcGkvcHJvdG9iBnByb3RvMw");
944
+ var ProofRequestSchema = /* @__PURE__ */ codegenv2.messageDesc(file_prover, 0);
945
+ var WitnessDataSchema = /* @__PURE__ */ codegenv2.messageDesc(file_prover, 1);
946
+ var LightClientDataSchema = /* @__PURE__ */ codegenv2.messageDesc(file_prover, 2);
947
+ var MPTProofDataSchema = /* @__PURE__ */ codegenv2.messageDesc(file_prover, 3);
948
+ var ProverService = /* @__PURE__ */ codegenv2.serviceDesc(file_prover, 0);
949
+
950
+ // src/prover.ts
951
+ exports.ProverClient = class ProverClient {
952
+ client;
953
+ pollIntervalMs;
954
+ maxPollAttempts;
955
+ constructor(proverUrl, options) {
956
+ const transport = connectWeb.createGrpcWebTransport({
957
+ baseUrl: proverUrl
958
+ });
959
+ this.client = connect.createClient(ProverService, transport);
960
+ this.pollIntervalMs = options?.pollIntervalMs ?? 2e3;
961
+ this.maxPollAttempts = options?.maxPollAttempts ?? 300;
962
+ }
963
+ /**
964
+ * Generate a proof by sending witness data to the prover server
965
+ *
966
+ * The server handles witness transformation internally, so we send
967
+ * the structured WitnessData instead of pre-serialized circuit witness bytes.
968
+ */
969
+ async generateProof(witness) {
970
+ const protoWitness = this.witnessToProto(witness);
971
+ const request = protobuf.create(ProofRequestSchema, {
972
+ witness: protoWitness
973
+ });
974
+ let attempts = 0;
975
+ while (attempts < this.maxPollAttempts) {
976
+ try {
977
+ const response = await this.client.poll(request);
978
+ switch (response.result.case) {
979
+ case "done":
980
+ return {
981
+ success: true,
982
+ proofJson: response.result.value.proofJson,
983
+ createdAt: new Date(Number(response.result.value.createdAt) * 1e3)
984
+ };
985
+ case "failed":
986
+ return {
987
+ success: false,
988
+ error: response.result.value.errorMessage
989
+ };
990
+ case "pending":
991
+ await this.sleep(this.pollIntervalMs);
992
+ attempts++;
993
+ break;
994
+ default:
995
+ await this.sleep(this.pollIntervalMs);
996
+ attempts++;
997
+ }
998
+ } catch (error) {
999
+ return {
1000
+ success: false,
1001
+ error: `RPC error: ${error instanceof Error ? error.message : String(error)}`
1002
+ };
1003
+ }
1004
+ }
1005
+ return {
1006
+ success: false,
1007
+ error: "Proof generation timed out"
1008
+ };
1009
+ }
1010
+ /**
1011
+ * Export the verifier contract from the prover server
1012
+ */
1013
+ async exportVerifier() {
1014
+ const response = await this.client.exportVerifier({});
1015
+ return response.verifierContract;
1016
+ }
1017
+ witnessToProto(witness) {
1018
+ return protobuf.create(WitnessDataSchema, {
1019
+ secret: witness.secret,
1020
+ dstChainId: witness.dstChainId,
1021
+ beneficiaries: [...witness.beneficiaries],
1022
+ beneficiary: witness.beneficiary,
1023
+ redeemAmount: witness.redeemAmount.toString(),
1024
+ alreadyRedeemed: witness.alreadyRedeemed.toString(),
1025
+ lightClients: witness.lightClients.map(
1026
+ (lc) => protobuf.create(LightClientDataSchema, {
1027
+ clientId: lc.clientId,
1028
+ height: lc.height,
1029
+ stateRoot: lc.stateRoot
1030
+ })
1031
+ ),
1032
+ selectedClientIndex: witness.selectedClientIndex,
1033
+ mptProof: protobuf.create(MPTProofDataSchema, {
1034
+ accountProof: [...witness.mptProof.accountProof],
1035
+ storageProof: [...witness.mptProof.storageProof],
1036
+ storageValue: witness.mptProof.storageValue,
1037
+ storageRoot: witness.mptProof.storageRoot
1038
+ }),
1039
+ tokenAddress: witness.srcZAssetAddress,
1040
+ mappingSlot: witness.mappingSlot
1041
+ });
1042
+ }
1043
+ sleep(ms) {
1044
+ return new Promise((resolve) => setTimeout(resolve, ms));
1045
+ }
1046
+ };
1047
+
1048
+ // src/attestation.ts
1049
+ exports.AttestationClient = class AttestationClient {
1050
+ baseUrl;
1051
+ apiKey;
1052
+ constructor(baseUrl, apiKey) {
1053
+ this.baseUrl = baseUrl.replace(/\/$/, "");
1054
+ this.apiKey = apiKey;
1055
+ }
1056
+ async getAttestation(unspendableAddress, beneficiary) {
1057
+ const request = {
1058
+ unspendableAddress,
1059
+ beneficiary
1060
+ };
1061
+ const headers = {
1062
+ "Content-Type": "application/json"
1063
+ };
1064
+ headers["x-api-key"] = this.apiKey;
1065
+ const response = await fetch(this.baseUrl, {
1066
+ method: "POST",
1067
+ headers,
1068
+ body: JSON.stringify(request)
1069
+ });
1070
+ if (!response.ok) {
1071
+ const errorText = await response.text();
1072
+ throw new Error(`Attestation service error: ${response.status} ${errorText}`);
1073
+ }
1074
+ const data = await response.json();
1075
+ const r = data.signature.r.slice(2);
1076
+ const s = data.signature.s.slice(2);
1077
+ const v = data.signature.v.toString(16).padStart(2, "0");
1078
+ const signature = `0x${r}${s}${v}`;
1079
+ return {
1080
+ attestedMessage: data.hash,
1081
+ signature
1082
+ };
1083
+ }
1084
+ };
1085
+ var publicClientLayer = (tag) => (...options) => effect.Layer.effect(
1086
+ tag,
1087
+ effect.pipe(
1088
+ effect.Effect.try({
1089
+ try: () => V__namespace.createPublicClient(...options),
1090
+ catch: (err) => new CreatePublicClientError({
1091
+ cause: err
1092
+ })
1093
+ }),
1094
+ effect.Effect.map((client) => ({ client }))
1095
+ )
1096
+ );
1097
+ var walletClientLayer = (tag) => (options) => effect.Layer.effect(
1098
+ tag,
1099
+ effect.pipe(
1100
+ effect.Effect.try({
1101
+ try: () => V__namespace.createWalletClient(options),
1102
+ catch: (err) => new CreateWalletClientError({
1103
+ cause: err
1104
+ })
1105
+ }),
1106
+ effect.Effect.map((client) => ({ client, account: options.account, chain: options.chain }))
1107
+ )
1108
+ );
1109
+ var WaitForTransactionReceiptError = class extends effect.Data.TaggedError(
1110
+ "WaitForTransactionReceiptError"
1111
+ ) {
1112
+ };
1113
+ var waitForTransactionReceipt = effect.Effect.fn("waitForTransactionReceipt")(
1114
+ (hash) => effect.pipe(
1115
+ PublicClient,
1116
+ effect.Effect.andThen(
1117
+ ({ client }) => effect.Effect.tryPromise({
1118
+ try: () => client.waitForTransactionReceipt({ hash }),
1119
+ catch: (err) => new WaitForTransactionReceiptError({
1120
+ cause: err
1121
+ })
1122
+ })
1123
+ )
1124
+ )
1125
+ );
1126
+ var readContract = effect.Effect.fn("readContract")(
1127
+ (params) => effect.pipe(
1128
+ PublicClient,
1129
+ effect.Effect.andThen(
1130
+ ({ client }) => effect.Effect.tryPromise({
1131
+ try: () => client.readContract(params),
1132
+ catch: (error) => new ReadContractError({
1133
+ cause: error
1134
+ })
1135
+ })
1136
+ )
1137
+ )
1138
+ );
1139
+ var writeContract = effect.Effect.fn("writeContract")(
1140
+ (params) => effect.pipe(
1141
+ WalletClient,
1142
+ effect.Effect.andThen(
1143
+ ({ client }) => effect.Effect.tryPromise({
1144
+ try: () => client.writeContract(params),
1145
+ catch: (error) => new WriteContractError({
1146
+ cause: error
1147
+ })
1148
+ })
1149
+ )
1150
+ )
1151
+ );
1152
+ effect.Effect.fn("writeContract")(
1153
+ (params) => effect.pipe(
1154
+ WalletClient,
1155
+ effect.Effect.andThen(
1156
+ ({ client }) => effect.Effect.tryPromise({
1157
+ try: () => client.writeContract(params),
1158
+ catch: (error) => new WriteContractError({
1159
+ cause: error
1160
+ })
1161
+ })
1162
+ )
1163
+ )
1164
+ );
1165
+ (class _ChannelDestination extends effect.Context.Tag("@unionlabs/sdk/Evm/ChannelDestination")() {
1166
+ static Live = effect.flow(
1167
+ _ChannelDestination.of,
1168
+ effect.Layer.succeed(this)
1169
+ );
1170
+ });
1171
+ (class _ChannelSource extends effect.Context.Tag("@unionlabs/sdk/Evm/ChannelSource")() {
1172
+ static Live = effect.flow(
1173
+ _ChannelSource.of,
1174
+ effect.Layer.succeed(this)
1175
+ );
1176
+ });
1177
+ (class extends effect.Context.Tag("@unionlabs/sdk/Evm/PublicClientSource")() {
1178
+ static Live = publicClientLayer(this);
1179
+ });
1180
+ (class extends effect.Context.Tag("@unionlabs/sdk/Evm/PublicClientDestination")() {
1181
+ static Live = publicClientLayer(this);
1182
+ });
1183
+ var PublicClient = class extends effect.Context.Tag("@unionlabs/sdk-evm/Evm/PublicClient")() {
1184
+ static Live = publicClientLayer(this);
1185
+ };
1186
+ var WalletClient = class extends effect.Context.Tag("@unionlabs/sdk/Evm/WalletClient")() {
1187
+ static Live = walletClientLayer(this);
1188
+ };
1189
+ var ReadContractError = class extends effect.Data.TaggedError("@unionlabs/sdk/Evm/ReadContractError") {
1190
+ };
1191
+ var WriteContractError = class extends effect.Data.TaggedError("@unionlabs/sdk/Evm/WriteContractError") {
1192
+ };
1193
+ (class extends effect.Data.TaggedError("@unionlabs/sdk/Evm/SimulateContractError") {
1194
+ });
1195
+ var CreatePublicClientError = class extends effect.Data.TaggedError("@unionlabs/sdk/Evm/CreatePublicClientError") {
1196
+ };
1197
+ var CreateWalletClientError = class extends effect.Data.TaggedError("@unionlabs/sdk/Evm/CreateWalletClientError") {
1198
+ };
1199
+
1200
+ // src/client.ts
1201
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1202
+ var commonRetry = {
1203
+ schedule: effect.Schedule.linear(effect.Duration.seconds(2)),
1204
+ times: 6
1205
+ };
1206
+ var make = (options) => {
1207
+ const proverUrl = options.proverUrl?.toString() ?? "https://prover.payments.union.build";
1208
+ const attestorUrl = options.attestationUrl?.toString() ?? "https://attestor.payments.union.build/functions/v1/attest";
1209
+ const zAssetAddress = Z_ASSET_REGISTRY[`${options.chainId}`][options.assetAddress];
1210
+ if (!zAssetAddress) throw new Error("Invalid asset address");
1211
+ if (!options.attestorApiKey) throw new Error("No attestor API key");
1212
+ return new exports.UnionPrivatePayments({
1213
+ proverUrl,
1214
+ sourceRpcUrl: options.rpcUrl.toString(),
1215
+ destinationRpcUrl: options.rpcUrl.toString(),
1216
+ srcZAssetAddress: zAssetAddress,
1217
+ dstZAssetAddress: zAssetAddress,
1218
+ sourceChainId: options.chainId,
1219
+ destinationChainId: options.chainId,
1220
+ attestorUrl,
1221
+ attestorApiKey: options.attestorApiKey
1222
+ });
1223
+ };
1224
+ exports.UnionPrivatePayments = class UnionPrivatePayments {
1225
+ config;
1226
+ srcClient;
1227
+ dstClient;
1228
+ proverClient;
1229
+ constructor(config) {
1230
+ this.config = config;
1231
+ this.srcClient = new exports.RpcClient(config.sourceRpcUrl);
1232
+ this.dstClient = new exports.RpcClient(config.destinationRpcUrl);
1233
+ this.proverClient = new exports.ProverClient(config.proverUrl);
1234
+ }
1235
+ /**
1236
+ * Get the deposit address for a given secret and beneficiaries
1237
+ *
1238
+ * The returned address is an "unspendable" address derived from the secret.
1239
+ * Tokens sent to this address can only be redeemed via ZK proof.
1240
+ *
1241
+ * @param secret - The 32-byte secret as a hex string
1242
+ * @param beneficiaries - Array of 1-4 beneficiary addresses (remaining slots zero-padded)
1243
+ * @returns The unspendable deposit address
1244
+ */
1245
+ getDepositAddress(secret, beneficiaries) {
1246
+ const paddedBeneficiaries = this.padBeneficiaries(beneficiaries);
1247
+ return computeUnspendableAddress(
1248
+ secret,
1249
+ this.config.destinationChainId,
1250
+ paddedBeneficiaries
1251
+ );
1252
+ }
1253
+ /**
1254
+ * Get the nullifier for a given paymentKey
1255
+ *
1256
+ * The nullifier is used to prevent double-spending. Each (paymentKey, chainId) pair
1257
+ * produces a unique nullifier that is recorded on-chain when funds are redeemed.
1258
+ *
1259
+ * @param paymentKey - The 32-byte paymentKey as a hex string
1260
+ * @returns The nullifier as a bigint
1261
+ */
1262
+ getNullifier(paymentKey) {
1263
+ return computeNullifier(paymentKey, this.config.destinationChainId);
1264
+ }
1265
+ /**
1266
+ * Get balance information
1267
+ *
1268
+ * Returns:
1269
+ * - confirmed: balance visible to light client (provable)
1270
+ * - redeemed: amount already redeemed via nullifier
1271
+ * - available: amount that can be redeemed now (confirmed - redeemed)
1272
+ * - pending: deposits not yet visible to light client
1273
+ *
1274
+ * @param options - Options for getting balance
1275
+ * @returns Balance information
1276
+ */
1277
+ async getBalance(options) {
1278
+ const { depositAddress, nullifier, clientId } = options;
1279
+ const ibcHandlerAddress = await this.dstClient.getIbcHandlerAddress(
1280
+ this.config.dstZAssetAddress
1281
+ );
1282
+ const lightClientAddress = await this.dstClient.getLightClientAddress(
1283
+ ibcHandlerAddress,
1284
+ clientId
1285
+ );
1286
+ const lightClientHeight = await this.dstClient.getLatestHeight(
1287
+ lightClientAddress,
1288
+ clientId
1289
+ );
1290
+ const balance = await this.getBalanceAtHeight(
1291
+ depositAddress,
1292
+ nullifier,
1293
+ lightClientHeight
1294
+ );
1295
+ return {
1296
+ ...balance,
1297
+ lightClientHeight
1298
+ };
1299
+ }
1300
+ /**
1301
+ * Get balance information at a specific block height
1302
+ *
1303
+ * @param depositAddress - The deposit address (unspendable address)
1304
+ * @param nullifier - The nullifier for this secret
1305
+ * @param height - The block height to query confirmed balance at
1306
+ * @returns Balance information at the given height
1307
+ */
1308
+ async getBalanceAtHeight(depositAddress, nullifier, height) {
1309
+ const latestHeight = await this.srcClient.getLatestBlockNumber();
1310
+ const [balanceAtTip, confirmed, redeemed] = await Promise.all([
1311
+ this.srcClient.getBalance(this.config.srcZAssetAddress, depositAddress),
1312
+ this.srcClient.getBalance(
1313
+ this.config.srcZAssetAddress,
1314
+ depositAddress,
1315
+ height
1316
+ ),
1317
+ this.dstClient.getNullifierBalance(
1318
+ this.config.dstZAssetAddress,
1319
+ nullifier
1320
+ )
1321
+ ]);
1322
+ const available = confirmed > redeemed ? confirmed - redeemed : 0n;
1323
+ const pending = balanceAtTip > confirmed ? balanceAtTip - confirmed : 0n;
1324
+ return {
1325
+ confirmed,
1326
+ redeemed,
1327
+ available,
1328
+ pending,
1329
+ latestHeight
1330
+ };
1331
+ }
1332
+ /**
1333
+ * Generate a proof for redeeming funds
1334
+ *
1335
+ * This method:
1336
+ * 1. Fetches light client data from the destination chain
1337
+ * 2. Deterministically shuffles clients for privacy
1338
+ * 3. Fetches MPT proof from the source chain
1339
+ * 4. Builds the witness data
1340
+ * 5. Sends the witness to the prover server
1341
+ * 6. Polls until the proof is ready
1342
+ *
1343
+ * @param secret - The 32-byte secret as a hex string
1344
+ * @param beneficiaries - Array of 0-4 beneficiary addresses (empty array = unbounded mode)
1345
+ * @param beneficiary - The beneficiary address to redeem to
1346
+ * @param amount - Amount to redeem
1347
+ * @param clientIds - Light client IDs to include in the proof (for anonymity set)
1348
+ * @param selectedClientId - The specific light client ID to use for the proof
1349
+ * @returns GenerateProofResult containing proof result and client-side metadata
1350
+ */
1351
+ async generateProof(secret, beneficiaries, beneficiary, amount, clientIds, selectedClientId) {
1352
+ if (!clientIds.includes(selectedClientId)) {
1353
+ return {
1354
+ proof: {
1355
+ success: false,
1356
+ error: `selectedClientId ${selectedClientId} not in clientIds`
1357
+ }
1358
+ };
1359
+ }
1360
+ if (beneficiary === ZERO_ADDRESS) {
1361
+ return {
1362
+ proof: {
1363
+ success: false,
1364
+ error: "Beneficiary address cannot be zero"
1365
+ }
1366
+ };
1367
+ }
1368
+ const paddedBeneficiaries = this.padBeneficiaries(beneficiaries);
1369
+ const depositAddress = this.getDepositAddress(secret, beneficiaries);
1370
+ const lightClients = await fetchLightClients(
1371
+ this.dstClient,
1372
+ this.config.dstZAssetAddress,
1373
+ clientIds
1374
+ );
1375
+ if (lightClients.length === 0) {
1376
+ return {
1377
+ proof: {
1378
+ success: false,
1379
+ error: "No valid light clients found"
1380
+ }
1381
+ };
1382
+ }
1383
+ const shuffled = deterministicShuffleClients(lightClients, secret);
1384
+ const selectedClientIndex = shuffled.findIndex(
1385
+ (c) => c.clientId === selectedClientId
1386
+ );
1387
+ if (selectedClientIndex === -1) {
1388
+ return {
1389
+ proof: {
1390
+ success: false,
1391
+ error: `Client ${selectedClientId} not found after fetching`
1392
+ }
1393
+ };
1394
+ }
1395
+ const selectedClient = shuffled[selectedClientIndex];
1396
+ const { tokenAddressKey, balanceSlot } = await this.dstClient.getCounterparty(
1397
+ this.config.dstZAssetAddress,
1398
+ selectedClientId
1399
+ );
1400
+ const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
1401
+ if (balanceSlot === ZERO_BYTES32 || tokenAddressKey === ZERO_BYTES32) {
1402
+ return {
1403
+ proof: {
1404
+ success: false,
1405
+ error: `Light client ${selectedClientId} is not configured as a counterparty on the destination ZAsset. Please call setCounterparty() on the ZAsset contract first.`
1406
+ }
1407
+ };
1408
+ }
1409
+ const mappingSlot = V.hexToBigInt(balanceSlot);
1410
+ const nullifier = this.getNullifier(secret);
1411
+ const balance = await this.getBalanceAtHeight(
1412
+ depositAddress,
1413
+ nullifier,
1414
+ selectedClient.height
1415
+ );
1416
+ if (amount > balance.available) {
1417
+ return {
1418
+ proof: {
1419
+ success: false,
1420
+ error: `Insufficient available balance. Requested: ${amount}, Available: ${balance.available} (Confirmed at height ${selectedClient.height}: ${balance.confirmed}, Already redeemed: ${balance.redeemed})`
1421
+ }
1422
+ };
1423
+ }
1424
+ const storageSlot = computeStorageSlot(depositAddress, mappingSlot);
1425
+ const mptProof = await fetchMptProof(
1426
+ this.srcClient,
1427
+ this.config.srcZAssetAddress,
1428
+ storageSlot,
1429
+ selectedClient.height
1430
+ );
1431
+ const witness = {
1432
+ secret,
1433
+ dstChainId: this.config.destinationChainId,
1434
+ beneficiaries: paddedBeneficiaries,
1435
+ beneficiary,
1436
+ redeemAmount: amount,
1437
+ alreadyRedeemed: balance.redeemed,
1438
+ lightClients: shuffled,
1439
+ selectedClientIndex,
1440
+ mptProof,
1441
+ srcZAssetAddress: this.config.srcZAssetAddress,
1442
+ mappingSlot: `0x${mappingSlot.toString(16)}`
1443
+ };
1444
+ const proofResult = await this.proverClient.generateProof(witness);
1445
+ if (proofResult.success) {
1446
+ return {
1447
+ proof: proofResult,
1448
+ metadata: {
1449
+ depositAddress,
1450
+ beneficiary,
1451
+ value: amount,
1452
+ lightClients: shuffled,
1453
+ nullifier
1454
+ }
1455
+ };
1456
+ }
1457
+ return { proof: proofResult };
1458
+ }
1459
+ /**
1460
+ * Export the verifier contract from the prover server
1461
+ *
1462
+ * The verifier contract is used to verify proofs on-chain.
1463
+ * It is circuit-specific and does not change between proofs.
1464
+ *
1465
+ * @returns The Solidity verifier contract source code
1466
+ */
1467
+ async exportVerifier() {
1468
+ return this.proverClient.exportVerifier();
1469
+ }
1470
+ /**
1471
+ * Get source ZAsset token information
1472
+ *
1473
+ * @returns Token symbol and decimals
1474
+ */
1475
+ async getSrcZAssetInfo() {
1476
+ const [symbol, decimals] = await Promise.all([
1477
+ this.srcClient.getSymbol(this.config.srcZAssetAddress),
1478
+ this.srcClient.getDecimals(this.config.srcZAssetAddress)
1479
+ ]);
1480
+ return { symbol, decimals };
1481
+ }
1482
+ /**
1483
+ * Get destination ZAsset token information
1484
+ *
1485
+ * @returns Token symbol and decimals
1486
+ */
1487
+ async getDstZAssetInfo() {
1488
+ const [symbol, decimals] = await Promise.all([
1489
+ this.dstClient.getSymbol(this.config.dstZAssetAddress),
1490
+ this.dstClient.getDecimals(this.config.dstZAssetAddress)
1491
+ ]);
1492
+ return { symbol, decimals };
1493
+ }
1494
+ /**
1495
+ * Deposit underlying tokens to ZAsset and transfer to deposit address
1496
+ *
1497
+ * Executes 3 transactions: approve → deposit → transfer.
1498
+ * The wallet client must be connected to the source chain.
1499
+ *
1500
+ * @param secret - The 32-byte secret as a hex string
1501
+ * @param beneficiaries - Array of 0-4 beneficiary addresses
1502
+ * @param amount - Amount to deposit (in underlying token's smallest unit)
1503
+ * @param walletClient - viem WalletClient with account and chain configured
1504
+ * @returns Transaction hash, deposit address, underlying token, and chain ID
1505
+ */
1506
+ async deposit(options) {
1507
+ const { paymentKey, beneficiaries, amount, walletClient } = options;
1508
+ if (!walletClient.account) {
1509
+ throw new Error("WalletClient must have an account");
1510
+ }
1511
+ if (!walletClient.chain) {
1512
+ throw new Error("WalletClient must have a chain configured");
1513
+ }
1514
+ return effect.Effect.gen(this, function* () {
1515
+ const depositAddress = this.getDepositAddress(paymentKey, beneficiaries);
1516
+ const underlyingToken = yield* effect.pipe(
1517
+ readContract({
1518
+ address: this.config.srcZAssetAddress,
1519
+ abi: [
1520
+ {
1521
+ inputs: [],
1522
+ name: "underlying",
1523
+ outputs: [{ name: "", type: "address" }],
1524
+ stateMutability: "view",
1525
+ type: "function"
1526
+ }
1527
+ ],
1528
+ functionName: "underlying"
1529
+ }),
1530
+ effect.Effect.retry(commonRetry),
1531
+ effect.Effect.provideService(PublicClient, {
1532
+ client: this.srcClient.getClient()
1533
+ })
1534
+ );
1535
+ if (underlyingToken === ZERO_ADDRESS) {
1536
+ return yield* effect.Effect.fail(
1537
+ Error("ZAsset is not a wrapped token (underlying is zero address)")
1538
+ );
1539
+ }
1540
+ const ERC20_ABI2 = [
1541
+ {
1542
+ inputs: [
1543
+ { name: "spender", type: "address" },
1544
+ { name: "amount", type: "uint256" }
1545
+ ],
1546
+ name: "approve",
1547
+ outputs: [{ name: "", type: "bool" }],
1548
+ stateMutability: "nonpayable",
1549
+ type: "function"
1550
+ }
1551
+ ];
1552
+ const ZASSET_ABI2 = [
1553
+ {
1554
+ inputs: [{ name: "amount", type: "uint256" }],
1555
+ name: "deposit",
1556
+ outputs: [],
1557
+ stateMutability: "nonpayable",
1558
+ type: "function"
1559
+ },
1560
+ {
1561
+ inputs: [
1562
+ { name: "to", type: "address" },
1563
+ { name: "amount", type: "uint256" }
1564
+ ],
1565
+ name: "transfer",
1566
+ outputs: [{ name: "", type: "bool" }],
1567
+ stateMutability: "nonpayable",
1568
+ type: "function"
1569
+ }
1570
+ ];
1571
+ const approveHash = yield* effect.pipe(
1572
+ writeContract({
1573
+ address: underlyingToken,
1574
+ abi: ERC20_ABI2,
1575
+ functionName: "approve",
1576
+ args: [this.config.srcZAssetAddress, amount],
1577
+ chain: walletClient.chain,
1578
+ account: walletClient.account
1579
+ }),
1580
+ effect.Effect.retry(commonRetry)
1581
+ );
1582
+ const approveReceipt = yield* effect.pipe(
1583
+ waitForTransactionReceipt(approveHash),
1584
+ effect.Effect.retry(commonRetry)
1585
+ );
1586
+ if (approveReceipt.status === "reverted") {
1587
+ return yield* effect.Effect.fail(
1588
+ Error(`Approve transaction reverted: ${approveHash}`)
1589
+ );
1590
+ }
1591
+ const depositHash = yield* effect.pipe(
1592
+ writeContract({
1593
+ address: this.config.srcZAssetAddress,
1594
+ abi: ZASSET_ABI2,
1595
+ functionName: "deposit",
1596
+ args: [amount],
1597
+ chain: walletClient.chain,
1598
+ account: walletClient.account
1599
+ }),
1600
+ effect.Effect.retry(commonRetry)
1601
+ );
1602
+ const depositReceipt = yield* waitForTransactionReceipt(depositHash).pipe(effect.Effect.retry(commonRetry));
1603
+ if (depositReceipt.status === "reverted") {
1604
+ throw new Error(`Deposit transaction reverted: ${depositHash}`);
1605
+ }
1606
+ const transferHash = yield* effect.pipe(
1607
+ writeContract({
1608
+ address: this.config.srcZAssetAddress,
1609
+ abi: ZASSET_ABI2,
1610
+ functionName: "transfer",
1611
+ args: [depositAddress, amount],
1612
+ chain: walletClient.chain,
1613
+ account: walletClient.account
1614
+ }),
1615
+ effect.Effect.retry(commonRetry)
1616
+ );
1617
+ const transferReceipt = yield* waitForTransactionReceipt(transferHash);
1618
+ if (transferReceipt.status === "reverted") {
1619
+ throw new Error(`Transfer transaction reverted: ${transferHash}`);
1620
+ }
1621
+ return {
1622
+ txHash: transferHash,
1623
+ depositAddress,
1624
+ underlyingToken,
1625
+ chainId: this.config.sourceChainId,
1626
+ height: transferReceipt.blockNumber
1627
+ };
1628
+ }).pipe(
1629
+ effect.Effect.provide(
1630
+ PublicClient.Live({
1631
+ chain: options.walletClient.chain,
1632
+ transport: V.http(this.config.sourceRpcUrl)
1633
+ })
1634
+ ),
1635
+ effect.Effect.provideService(WalletClient, {
1636
+ client: options.walletClient,
1637
+ account: options.walletClient.account,
1638
+ chain: options.walletClient.chain
1639
+ }),
1640
+ effect.Effect.runPromise
1641
+ );
1642
+ }
1643
+ /**
1644
+ * Deposit underlying tokens to ZAsset and transfer to deposit address
1645
+ *
1646
+ * Executes 3 transactions: approve → deposit → transfer.
1647
+ * The wallet client must be connected to the source chain.
1648
+ *
1649
+ * @param secret - The 32-byte secret as a hex string
1650
+ * @param beneficiaries - Array of 0-4 beneficiary addresses
1651
+ * @param amount - Amount to deposit (in underlying token's smallest unit)
1652
+ * @param walletClient - viem WalletClient with account and chain configured
1653
+ * @returns Transaction hash, deposit address, underlying token, and chain ID
1654
+ */
1655
+ async unsafeDeposit(options) {
1656
+ const { paymentKey, beneficiaries, amount, walletClient } = options;
1657
+ if (!walletClient.account) {
1658
+ throw new Error("WalletClient must have an account");
1659
+ }
1660
+ if (!walletClient.chain) {
1661
+ throw new Error("WalletClient must have a chain configured");
1662
+ }
1663
+ const depositAddress = this.getDepositAddress(paymentKey, beneficiaries);
1664
+ const publicClient = V.createPublicClient({
1665
+ chain: walletClient.chain,
1666
+ transport: V.http(this.config.sourceRpcUrl)
1667
+ });
1668
+ const underlyingToken = await this.srcClient.getClient().readContract({
1669
+ address: this.config.srcZAssetAddress,
1670
+ abi: [
1671
+ {
1672
+ inputs: [],
1673
+ name: "underlying",
1674
+ outputs: [{ name: "", type: "address" }],
1675
+ stateMutability: "view",
1676
+ type: "function"
1677
+ }
1678
+ ],
1679
+ functionName: "underlying"
1680
+ });
1681
+ if (underlyingToken === ZERO_ADDRESS) {
1682
+ throw new Error(
1683
+ "ZAsset is not a wrapped token (underlying is zero address)"
1684
+ );
1685
+ }
1686
+ const ERC20_ABI2 = [
1687
+ {
1688
+ inputs: [
1689
+ { name: "spender", type: "address" },
1690
+ { name: "amount", type: "uint256" }
1691
+ ],
1692
+ name: "approve",
1693
+ outputs: [{ name: "", type: "bool" }],
1694
+ stateMutability: "nonpayable",
1695
+ type: "function"
1696
+ }
1697
+ ];
1698
+ const ZASSET_ABI2 = [
1699
+ {
1700
+ inputs: [{ name: "amount", type: "uint256" }],
1701
+ name: "deposit",
1702
+ outputs: [],
1703
+ stateMutability: "nonpayable",
1704
+ type: "function"
1705
+ },
1706
+ {
1707
+ inputs: [
1708
+ { name: "to", type: "address" },
1709
+ { name: "amount", type: "uint256" }
1710
+ ],
1711
+ name: "transfer",
1712
+ outputs: [{ name: "", type: "bool" }],
1713
+ stateMutability: "nonpayable",
1714
+ type: "function"
1715
+ }
1716
+ ];
1717
+ const approveHash = await walletClient.writeContractSync({
1718
+ address: underlyingToken,
1719
+ abi: ERC20_ABI2,
1720
+ functionName: "approve",
1721
+ args: [this.config.srcZAssetAddress, amount],
1722
+ chain: walletClient.chain,
1723
+ account: walletClient.account
1724
+ }).then((x) => x.transactionHash);
1725
+ const approveReceipt = await publicClient.waitForTransactionReceipt({
1726
+ hash: approveHash
1727
+ });
1728
+ if (approveReceipt.status === "reverted") {
1729
+ throw new Error(`Approve transaction reverted: ${approveHash}`);
1730
+ }
1731
+ const depositHash = await walletClient.writeContractSync({
1732
+ address: this.config.srcZAssetAddress,
1733
+ abi: ZASSET_ABI2,
1734
+ functionName: "deposit",
1735
+ args: [amount],
1736
+ chain: walletClient.chain,
1737
+ account: walletClient.account
1738
+ }).then((x) => x.transactionHash);
1739
+ const depositReceipt = await publicClient.waitForTransactionReceipt({
1740
+ hash: depositHash
1741
+ });
1742
+ if (depositReceipt.status === "reverted") {
1743
+ throw new Error(`Deposit transaction reverted: ${depositHash}`);
1744
+ }
1745
+ const transferHash = await walletClient.writeContractSync({
1746
+ address: this.config.srcZAssetAddress,
1747
+ abi: ZASSET_ABI2,
1748
+ functionName: "transfer",
1749
+ args: [depositAddress, amount],
1750
+ chain: walletClient.chain,
1751
+ account: walletClient.account
1752
+ }).then((x) => x.transactionHash);
1753
+ const transferReceipt = await publicClient.waitForTransactionReceipt({
1754
+ hash: transferHash
1755
+ });
1756
+ if (transferReceipt.status === "reverted") {
1757
+ throw new Error(`Transfer transaction reverted: ${transferHash}`);
1758
+ }
1759
+ return {
1760
+ txHash: transferHash,
1761
+ depositAddress,
1762
+ underlyingToken,
1763
+ chainId: this.config.sourceChainId,
1764
+ height: transferReceipt.blockNumber
1765
+ };
1766
+ }
1767
+ /**
1768
+ * Update loopback light client to a specific block height
1769
+ *
1770
+ * Fetches the IBC handler address internally from the destination ZAsset.
1771
+ * The wallet client must be connected to the destination chain.
1772
+ *
1773
+ * @returns Transaction hash, block number, state root, and chain ID
1774
+ */
1775
+ async updateLightClient(options) {
1776
+ const { clientId, height, walletClient } = options;
1777
+ if (!walletClient.account) {
1778
+ throw new Error("WalletClient must have an account");
1779
+ }
1780
+ if (!walletClient.chain) {
1781
+ throw new Error("WalletClient must have a chain configured");
1782
+ }
1783
+ return effect.Effect.gen(this, function* () {
1784
+ const publicClient = V.createPublicClient({
1785
+ chain: walletClient.chain,
1786
+ transport: V.http(this.config.destinationRpcUrl)
1787
+ });
1788
+ const ibcHandlerAddress = yield* effect.Effect.tryPromise({
1789
+ try: () => this.dstClient.getIbcHandlerAddress(this.config.dstZAssetAddress),
1790
+ catch: (cause) => effect.Effect.fail(cause)
1791
+ });
1792
+ const blockNumber = height === "latest" ? yield* effect.pipe(
1793
+ effect.Effect.tryPromise(() => publicClient.getBlockNumber()),
1794
+ effect.Effect.retry(commonRetry)
1795
+ ) : height;
1796
+ const block = yield* effect.pipe(
1797
+ effect.Effect.tryPromise(() => publicClient.getBlock({ blockNumber })),
1798
+ effect.Effect.retry(commonRetry)
1799
+ );
1800
+ if (!block.number) {
1801
+ throw new Error("Block number is null");
1802
+ }
1803
+ const toRlpHex = (value) => {
1804
+ if (value === void 0 || value === null || value === 0n || value === 0) {
1805
+ return "0x";
1806
+ }
1807
+ let hex = typeof value === "bigint" ? value.toString(16) : value.toString(16);
1808
+ if (hex.length % 2 !== 0) {
1809
+ hex = "0" + hex;
1810
+ }
1811
+ return `0x${hex}`;
1812
+ };
1813
+ const headerFields = [
1814
+ block.parentHash,
1815
+ block.sha3Uncles,
1816
+ block.miner,
1817
+ block.stateRoot,
1818
+ block.transactionsRoot,
1819
+ block.receiptsRoot,
1820
+ block.logsBloom ?? "0x" + "00".repeat(256),
1821
+ toRlpHex(block.difficulty),
1822
+ toRlpHex(block.number),
1823
+ toRlpHex(block.gasLimit),
1824
+ toRlpHex(block.gasUsed),
1825
+ toRlpHex(block.timestamp),
1826
+ block.extraData,
1827
+ block.mixHash ?? "0x0000000000000000000000000000000000000000000000000000000000000000",
1828
+ block.nonce ?? "0x0000000000000000"
1829
+ ];
1830
+ if (block.baseFeePerGas !== void 0 && block.baseFeePerGas !== null) {
1831
+ headerFields.push(toRlpHex(block.baseFeePerGas));
1832
+ }
1833
+ if (block.withdrawalsRoot) {
1834
+ headerFields.push(block.withdrawalsRoot);
1835
+ }
1836
+ if (block.blobGasUsed !== void 0 && block.blobGasUsed !== null) {
1837
+ headerFields.push(toRlpHex(block.blobGasUsed));
1838
+ }
1839
+ if (block.excessBlobGas !== void 0 && block.excessBlobGas !== null) {
1840
+ headerFields.push(toRlpHex(block.excessBlobGas));
1841
+ }
1842
+ if (block.parentBeaconBlockRoot) {
1843
+ headerFields.push(block.parentBeaconBlockRoot);
1844
+ }
1845
+ const blockAny = block;
1846
+ if (blockAny.requestsHash) {
1847
+ headerFields.push(blockAny.requestsHash);
1848
+ }
1849
+ const rlpEncoded = V.toRlp(headerFields);
1850
+ const computedHash = V.keccak256(rlpEncoded);
1851
+ if (computedHash !== block.hash) {
1852
+ throw new Error(
1853
+ `RLP encoding mismatch: computed hash ${computedHash} does not match block hash ${block.hash}`
1854
+ );
1855
+ }
1856
+ const clientMessage = V.encodeAbiParameters(
1857
+ [
1858
+ { type: "uint64", name: "height" },
1859
+ { type: "bytes", name: "encodedHeader" }
1860
+ ],
1861
+ [block.number, rlpEncoded]
1862
+ );
1863
+ const LIGHTCLIENT_ABI = [
1864
+ {
1865
+ inputs: [
1866
+ { name: "caller", type: "address" },
1867
+ { name: "clientId", type: "uint32" },
1868
+ { name: "clientMessage", type: "bytes" },
1869
+ { name: "relayer", type: "address" }
1870
+ ],
1871
+ name: "updateClient",
1872
+ outputs: [],
1873
+ stateMutability: "nonpayable",
1874
+ type: "function"
1875
+ }
1876
+ ];
1877
+ let currentBlock = yield* effect.pipe(
1878
+ effect.Effect.tryPromise(() => publicClient.getBlockNumber()),
1879
+ effect.Effect.retry(commonRetry)
1880
+ );
1881
+ while (currentBlock <= blockNumber) {
1882
+ yield* effect.Effect.sleep("1 second");
1883
+ currentBlock = yield* effect.Effect.tryPromise(
1884
+ () => publicClient.getBlockNumber()
1885
+ );
1886
+ }
1887
+ const loopbackClient = yield* effect.pipe(
1888
+ readContract({
1889
+ address: ibcHandlerAddress,
1890
+ abi: IBC_STORE_ABI,
1891
+ functionName: "getClient",
1892
+ args: [clientId]
1893
+ }),
1894
+ effect.Effect.retry(commonRetry)
1895
+ );
1896
+ const txHash = yield* effect.pipe(
1897
+ writeContract({
1898
+ address: loopbackClient,
1899
+ abi: LIGHTCLIENT_ABI,
1900
+ functionName: "updateClient",
1901
+ args: [
1902
+ walletClient.account.address,
1903
+ clientId,
1904
+ clientMessage,
1905
+ walletClient.account.address
1906
+ ],
1907
+ chain: walletClient.chain,
1908
+ account: walletClient.account
1909
+ }),
1910
+ effect.Effect.retry(commonRetry)
1911
+ );
1912
+ const chainId = yield* effect.Effect.sync(() => this.config.destinationChainId);
1913
+ return {
1914
+ txHash,
1915
+ blockNumber: block.number,
1916
+ stateRoot: block.stateRoot,
1917
+ chainId
1918
+ };
1919
+ }).pipe(
1920
+ effect.Effect.provide(
1921
+ PublicClient.Live({
1922
+ chain: walletClient.chain,
1923
+ transport: V.http(this.config.destinationRpcUrl)
1924
+ })
1925
+ ),
1926
+ effect.Effect.provideService(WalletClient, {
1927
+ client: options.walletClient,
1928
+ account: options.walletClient.account,
1929
+ chain: options.walletClient.chain
1930
+ }),
1931
+ effect.Effect.runPromise
1932
+ );
1933
+ }
1934
+ /**
1935
+ * Update loopback light client to a specific block height
1936
+ *
1937
+ * Fetches the IBC handler address internally from the destination ZAsset.
1938
+ * The wallet client must be connected to the destination chain.
1939
+ *
1940
+ * @returns Transaction hash, block number, state root, and chain ID
1941
+ */
1942
+ async unsafeUpdateLightClient(options) {
1943
+ const { clientId, height, walletClient } = options;
1944
+ if (!walletClient.account) {
1945
+ throw new Error("WalletClient must have an account");
1946
+ }
1947
+ if (!walletClient.chain) {
1948
+ throw new Error("WalletClient must have a chain configured");
1949
+ }
1950
+ const publicClient = V.createPublicClient({
1951
+ chain: walletClient.chain,
1952
+ transport: V.http(this.config.destinationRpcUrl)
1953
+ });
1954
+ const ibcHandlerAddress = await this.dstClient.getIbcHandlerAddress(
1955
+ this.config.dstZAssetAddress
1956
+ );
1957
+ const blockNumber = height === "latest" ? await publicClient.getBlockNumber() : height;
1958
+ const block = await publicClient.getBlock({ blockNumber });
1959
+ if (!block.number) {
1960
+ throw new Error("Block number is null");
1961
+ }
1962
+ const toRlpHex = (value) => {
1963
+ if (value === void 0 || value === null || value === 0n || value === 0) {
1964
+ return "0x";
1965
+ }
1966
+ let hex = typeof value === "bigint" ? value.toString(16) : value.toString(16);
1967
+ if (hex.length % 2 !== 0) {
1968
+ hex = "0" + hex;
1969
+ }
1970
+ return `0x${hex}`;
1971
+ };
1972
+ const headerFields = [
1973
+ block.parentHash,
1974
+ block.sha3Uncles,
1975
+ block.miner,
1976
+ block.stateRoot,
1977
+ block.transactionsRoot,
1978
+ block.receiptsRoot,
1979
+ block.logsBloom ?? "0x" + "00".repeat(256),
1980
+ toRlpHex(block.difficulty),
1981
+ toRlpHex(block.number),
1982
+ toRlpHex(block.gasLimit),
1983
+ toRlpHex(block.gasUsed),
1984
+ toRlpHex(block.timestamp),
1985
+ block.extraData,
1986
+ block.mixHash ?? "0x0000000000000000000000000000000000000000000000000000000000000000",
1987
+ block.nonce ?? "0x0000000000000000"
1988
+ ];
1989
+ if (block.baseFeePerGas !== void 0 && block.baseFeePerGas !== null) {
1990
+ headerFields.push(toRlpHex(block.baseFeePerGas));
1991
+ }
1992
+ if (block.withdrawalsRoot) {
1993
+ headerFields.push(block.withdrawalsRoot);
1994
+ }
1995
+ if (block.blobGasUsed !== void 0 && block.blobGasUsed !== null) {
1996
+ headerFields.push(toRlpHex(block.blobGasUsed));
1997
+ }
1998
+ if (block.excessBlobGas !== void 0 && block.excessBlobGas !== null) {
1999
+ headerFields.push(toRlpHex(block.excessBlobGas));
2000
+ }
2001
+ if (block.parentBeaconBlockRoot) {
2002
+ headerFields.push(block.parentBeaconBlockRoot);
2003
+ }
2004
+ const blockAny = block;
2005
+ if (blockAny.requestsHash) {
2006
+ headerFields.push(blockAny.requestsHash);
2007
+ }
2008
+ const rlpEncoded = V.toRlp(headerFields);
2009
+ const computedHash = V.keccak256(rlpEncoded);
2010
+ if (computedHash !== block.hash) {
2011
+ throw new Error(
2012
+ `RLP encoding mismatch: computed hash ${computedHash} does not match block hash ${block.hash}`
2013
+ );
2014
+ }
2015
+ const clientMessage = V.encodeAbiParameters(
2016
+ [
2017
+ { type: "uint64", name: "height" },
2018
+ { type: "bytes", name: "encodedHeader" }
2019
+ ],
2020
+ [block.number, rlpEncoded]
2021
+ );
2022
+ const LIGHTCLIENT_ABI = [
2023
+ {
2024
+ inputs: [
2025
+ { name: "caller", type: "address" },
2026
+ { name: "clientId", type: "uint32" },
2027
+ { name: "clientMessage", type: "bytes" },
2028
+ { name: "relayer", type: "address" }
2029
+ ],
2030
+ name: "updateClient",
2031
+ outputs: [],
2032
+ stateMutability: "nonpayable",
2033
+ type: "function"
2034
+ }
2035
+ ];
2036
+ let currentBlock = await publicClient.getBlockNumber();
2037
+ while (currentBlock <= blockNumber) {
2038
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
2039
+ currentBlock = await publicClient.getBlockNumber();
2040
+ }
2041
+ const loopbackClient = await publicClient.readContract({
2042
+ address: ibcHandlerAddress,
2043
+ abi: IBC_STORE_ABI,
2044
+ functionName: "getClient",
2045
+ args: [clientId]
2046
+ });
2047
+ const txHash = await walletClient.writeContractSync({
2048
+ address: loopbackClient,
2049
+ abi: LIGHTCLIENT_ABI,
2050
+ functionName: "updateClient",
2051
+ args: [
2052
+ walletClient.account.address,
2053
+ clientId,
2054
+ clientMessage,
2055
+ walletClient.account.address
2056
+ ],
2057
+ chain: walletClient.chain,
2058
+ account: walletClient.account
2059
+ }).then((x) => x.transactionHash);
2060
+ return {
2061
+ txHash,
2062
+ blockNumber: block.number,
2063
+ stateRoot: block.stateRoot,
2064
+ chainId: this.config.destinationChainId
2065
+ };
2066
+ }
2067
+ /**
2068
+ * Get redemption history for a secret
2069
+ *
2070
+ * Queries the Redeemed events filtered by the nullifier derived from the secret.
2071
+ * This is a read-only operation that doesn't require a wallet.
2072
+ *
2073
+ * @param secret - The 32-byte secret as a hex string
2074
+ * @param fromBlock - Start block number (default: earliest)
2075
+ * @param toBlock - End block number (default: latest)
2076
+ * @returns Array of redemption transactions
2077
+ */
2078
+ getRedemptionHistory(secret, fromBlock, toBlock) {
2079
+ return effect.Effect.gen(this, function* () {
2080
+ const nullifier = this.getNullifier(secret);
2081
+ return yield* effect.pipe(
2082
+ effect.Effect.tryPromise(
2083
+ () => this.dstClient.getRedemptionHistory(
2084
+ this.config.dstZAssetAddress,
2085
+ nullifier,
2086
+ fromBlock,
2087
+ toBlock
2088
+ )
2089
+ ),
2090
+ effect.Effect.retry(commonRetry)
2091
+ );
2092
+ }).pipe(effect.Effect.runPromise);
2093
+ }
2094
+ /**
2095
+ * Get redemption history for a secret
2096
+ *
2097
+ * Queries the Redeemed events filtered by the nullifier derived from the secret.
2098
+ * This is a read-only operation that doesn't require a wallet.
2099
+ *
2100
+ * @param secret - The 32-byte secret as a hex string
2101
+ * @param fromBlock - Start block number (default: earliest)
2102
+ * @param toBlock - End block number (default: latest)
2103
+ * @returns Array of redemption transactions
2104
+ */
2105
+ async unsafeGetRedemptionHistory(secret, fromBlock, toBlock) {
2106
+ const nullifier = this.getNullifier(secret);
2107
+ return this.dstClient.getRedemptionHistory(
2108
+ this.config.dstZAssetAddress,
2109
+ nullifier,
2110
+ fromBlock,
2111
+ toBlock
2112
+ );
2113
+ }
2114
+ /**
2115
+ * Full redeem flow: generate proof + get attestation + submit transaction
2116
+ *
2117
+ * This method orchestrates the entire redemption process:
2118
+ * 1. Generates a ZK proof via the prover server
2119
+ * 2. Gets attestation from the attestation service
2120
+ * 3. Submits the redeem transaction
2121
+ *
2122
+ * The wallet client must be connected to the destination chain.
2123
+ *
2124
+ * @returns Transaction hash, proof, and metadata
2125
+ */
2126
+ redeem(options) {
2127
+ const {
2128
+ paymentKey,
2129
+ beneficiaries,
2130
+ beneficiary,
2131
+ amount,
2132
+ clientIds,
2133
+ selectedClientId,
2134
+ walletClient,
2135
+ unwrap = true
2136
+ } = options;
2137
+ const attestationUrl = this.config.attestorUrl;
2138
+ const attestorApiKey = this.config.attestorApiKey;
2139
+ if (!attestationUrl) {
2140
+ throw Error(
2141
+ "Attestation URL must be provided either in redeem options or ClientOptions"
2142
+ );
2143
+ }
2144
+ if (!attestorApiKey) {
2145
+ throw Error(
2146
+ "Attestation API key must be provided either in redeem options or ClientOptions"
2147
+ );
2148
+ }
2149
+ if (!walletClient.account) {
2150
+ throw Error("WalletClient must have an account");
2151
+ }
2152
+ if (!walletClient.chain) {
2153
+ throw Error("WalletClient must have a chain configured");
2154
+ }
2155
+ return effect.Effect.gen(this, function* () {
2156
+ const proofResult = yield* effect.Effect.tryPromise({
2157
+ try: () => this.generateProof(
2158
+ paymentKey,
2159
+ beneficiaries,
2160
+ beneficiary,
2161
+ amount,
2162
+ clientIds,
2163
+ selectedClientId
2164
+ ),
2165
+ catch: (cause) => effect.Effect.fail(cause)
2166
+ }).pipe(effect.Effect.retry(commonRetry));
2167
+ if (!proofResult.proof.success || !proofResult.proof.proofJson || !proofResult.metadata) {
2168
+ return yield* effect.Effect.fail(
2169
+ Error(proofResult.proof.error ?? "Proof generation failed")
2170
+ );
2171
+ }
2172
+ const proofJson = parseProofJson(proofResult.proof.proofJson);
2173
+ const metadata = proofResult.metadata;
2174
+ const depositAddress = this.getDepositAddress(paymentKey, beneficiaries);
2175
+ const attestationClient = new exports.AttestationClient(
2176
+ attestationUrl,
2177
+ attestorApiKey
2178
+ );
2179
+ const attestationResponse = yield* effect.Effect.tryPromise({
2180
+ try: () => attestationClient.getAttestation(depositAddress, beneficiary),
2181
+ catch: (cause) => effect.Effect.fail(cause)
2182
+ }).pipe(effect.Effect.retry(commonRetry));
2183
+ const redeemParams = proofJsonToRedeemParams(proofJson, {
2184
+ lightClients: metadata.lightClients,
2185
+ nullifier: metadata.nullifier,
2186
+ value: metadata.value,
2187
+ beneficiary: metadata.beneficiary,
2188
+ attestedMessage: attestationResponse.attestedMessage,
2189
+ signature: attestationResponse.signature
2190
+ });
2191
+ const ZASSET_ABI2 = [
2192
+ {
2193
+ inputs: [
2194
+ { name: "proof", type: "uint256[8]" },
2195
+ { name: "commitments", type: "uint256[2]" },
2196
+ { name: "commitmentPok", type: "uint256[2]" },
2197
+ {
2198
+ name: "lightClients",
2199
+ type: "tuple[]",
2200
+ components: [
2201
+ { name: "clientId", type: "uint32" },
2202
+ { name: "height", type: "uint64" }
2203
+ ]
2204
+ },
2205
+ { name: "nullifier", type: "uint256" },
2206
+ { name: "value", type: "uint256" },
2207
+ { name: "beneficiary", type: "address" },
2208
+ { name: "attestedMessage", type: "bytes32" },
2209
+ { name: "signature", type: "bytes" },
2210
+ { name: "unwrap", type: "bool" }
2211
+ ],
2212
+ name: "redeem",
2213
+ outputs: [],
2214
+ stateMutability: "nonpayable",
2215
+ type: "function"
2216
+ }
2217
+ ];
2218
+ const txHash = yield* effect.pipe(
2219
+ writeContract({
2220
+ address: this.config.dstZAssetAddress,
2221
+ abi: ZASSET_ABI2,
2222
+ functionName: "redeem",
2223
+ args: [
2224
+ redeemParams.proof,
2225
+ redeemParams.commitments,
2226
+ redeemParams.commitmentPok,
2227
+ redeemParams.lightClients,
2228
+ redeemParams.nullifier,
2229
+ redeemParams.value,
2230
+ redeemParams.beneficiary,
2231
+ redeemParams.attestedMessage,
2232
+ redeemParams.signature,
2233
+ unwrap
2234
+ ],
2235
+ chain: walletClient.chain,
2236
+ account: walletClient.account
2237
+ }),
2238
+ effect.Effect.retry(commonRetry)
2239
+ );
2240
+ return {
2241
+ txHash,
2242
+ proof: proofJson,
2243
+ metadata
2244
+ };
2245
+ }).pipe(
2246
+ effect.Effect.provide(
2247
+ PublicClient.Live({
2248
+ chain: options.walletClient.chain,
2249
+ transport: V.http(this.config.destinationRpcUrl)
2250
+ })
2251
+ ),
2252
+ effect.Effect.provideService(WalletClient, {
2253
+ client: options.walletClient,
2254
+ account: options.walletClient.account,
2255
+ chain: options.walletClient.chain
2256
+ }),
2257
+ effect.Effect.runPromise
2258
+ );
2259
+ }
2260
+ /**
2261
+ * Full redeem flow: generate proof + get attestation + submit transaction
2262
+ *
2263
+ * This method orchestrates the entire redemption process:
2264
+ * 1. Generates a ZK proof via the prover server
2265
+ * 2. Gets attestation from the attestation service
2266
+ * 3. Submits the redeem transaction
2267
+ *
2268
+ * The wallet client must be connected to the destination chain.
2269
+ *
2270
+ * @returns Transaction hash, proof, and metadata
2271
+ */
2272
+ async unsafeRedeem(options) {
2273
+ const {
2274
+ paymentKey,
2275
+ beneficiaries,
2276
+ beneficiary,
2277
+ amount,
2278
+ clientIds,
2279
+ selectedClientId,
2280
+ walletClient,
2281
+ unwrap = true
2282
+ } = options;
2283
+ const attestationUrl = this.config.attestorUrl;
2284
+ const attestorApiKey = this.config.attestorApiKey;
2285
+ if (!attestationUrl) {
2286
+ throw new Error(
2287
+ "Attestation URL must be provided either in redeem options or ClientOptions"
2288
+ );
2289
+ }
2290
+ if (!attestorApiKey) {
2291
+ throw new Error(
2292
+ "Attestation API key must be provided either in redeem options or ClientOptions"
2293
+ );
2294
+ }
2295
+ if (!walletClient.account) {
2296
+ throw new Error("WalletClient must have an account");
2297
+ }
2298
+ if (!walletClient.chain) {
2299
+ throw new Error("WalletClient must have a chain configured");
2300
+ }
2301
+ const proofResult = await this.generateProof(
2302
+ paymentKey,
2303
+ beneficiaries,
2304
+ beneficiary,
2305
+ amount,
2306
+ clientIds,
2307
+ selectedClientId
2308
+ );
2309
+ if (!proofResult.proof.success || !proofResult.proof.proofJson || !proofResult.metadata) {
2310
+ throw new Error(proofResult.proof.error ?? "Proof generation failed");
2311
+ }
2312
+ const proofJson = parseProofJson(proofResult.proof.proofJson);
2313
+ const metadata = proofResult.metadata;
2314
+ const depositAddress = this.getDepositAddress(paymentKey, beneficiaries);
2315
+ const attestationClient = new exports.AttestationClient(
2316
+ attestationUrl,
2317
+ attestorApiKey
2318
+ );
2319
+ const attestationResponse = await attestationClient.getAttestation(
2320
+ depositAddress,
2321
+ beneficiary
2322
+ );
2323
+ const redeemParams = proofJsonToRedeemParams(proofJson, {
2324
+ lightClients: metadata.lightClients,
2325
+ nullifier: metadata.nullifier,
2326
+ value: metadata.value,
2327
+ beneficiary: metadata.beneficiary,
2328
+ attestedMessage: attestationResponse.attestedMessage,
2329
+ signature: attestationResponse.signature
2330
+ });
2331
+ const publicClient = V.createPublicClient({
2332
+ chain: walletClient.chain,
2333
+ transport: V.http(this.config.destinationRpcUrl)
2334
+ });
2335
+ const ZASSET_ABI2 = [
2336
+ {
2337
+ inputs: [
2338
+ { name: "proof", type: "uint256[8]" },
2339
+ { name: "commitments", type: "uint256[2]" },
2340
+ { name: "commitmentPok", type: "uint256[2]" },
2341
+ {
2342
+ name: "lightClients",
2343
+ type: "tuple[]",
2344
+ components: [
2345
+ { name: "clientId", type: "uint32" },
2346
+ { name: "height", type: "uint64" }
2347
+ ]
2348
+ },
2349
+ { name: "nullifier", type: "uint256" },
2350
+ { name: "value", type: "uint256" },
2351
+ { name: "beneficiary", type: "address" },
2352
+ { name: "attestedMessage", type: "bytes32" },
2353
+ { name: "signature", type: "bytes" },
2354
+ { name: "unwrap", type: "bool" }
2355
+ ],
2356
+ name: "redeem",
2357
+ outputs: [],
2358
+ stateMutability: "nonpayable",
2359
+ type: "function"
2360
+ }
2361
+ ];
2362
+ const txHash = await walletClient.writeContractSync({
2363
+ address: this.config.dstZAssetAddress,
2364
+ abi: ZASSET_ABI2,
2365
+ functionName: "redeem",
2366
+ args: [
2367
+ redeemParams.proof,
2368
+ redeemParams.commitments,
2369
+ redeemParams.commitmentPok,
2370
+ redeemParams.lightClients,
2371
+ redeemParams.nullifier,
2372
+ redeemParams.value,
2373
+ redeemParams.beneficiary,
2374
+ redeemParams.attestedMessage,
2375
+ redeemParams.signature,
2376
+ unwrap
2377
+ ],
2378
+ chain: walletClient.chain,
2379
+ account: walletClient.account
2380
+ }).then((x) => x.transactionHash);
2381
+ const receipt = await publicClient.waitForTransactionReceipt({
2382
+ hash: txHash
2383
+ });
2384
+ if (receipt.status === "reverted") {
2385
+ throw new Error(`Redeem transaction reverted: ${txHash}`);
2386
+ }
2387
+ return {
2388
+ txHash,
2389
+ proof: proofJson,
2390
+ metadata
2391
+ };
2392
+ }
2393
+ /**
2394
+ * Pad beneficiaries array to exactly 4 addresses
2395
+ * Empty array = unbounded mode (all zeros, any beneficiary allowed)
2396
+ */
2397
+ padBeneficiaries(beneficiaries) {
2398
+ if (beneficiaries.length > 4) {
2399
+ throw new Error("Maximum 4 beneficiaries allowed");
2400
+ }
2401
+ const padded = [
2402
+ beneficiaries[0] ?? ZERO_ADDRESS,
2403
+ beneficiaries[1] ?? ZERO_ADDRESS,
2404
+ beneficiaries[2] ?? ZERO_ADDRESS,
2405
+ beneficiaries[3] ?? ZERO_ADDRESS
2406
+ ];
2407
+ return padded;
2408
+ }
2409
+ };
2410
+ exports.waitForBlockCondition = (publicClient, predicate, options) => {
2411
+ const timeoutMs = options?.timeoutMs ?? 6e4;
2412
+ return new Promise((resolve, reject) => {
2413
+ let done = false;
2414
+ const unwatch = publicClient.watchBlockNumber({
2415
+ onBlockNumber(blockNumber) {
2416
+ if (done) return;
2417
+ if (predicate(blockNumber)) {
2418
+ done = true;
2419
+ unwatch();
2420
+ resolve(blockNumber);
2421
+ }
2422
+ },
2423
+ onError(error) {
2424
+ if (done) return;
2425
+ done = true;
2426
+ unwatch();
2427
+ reject(error);
2428
+ }
2429
+ });
2430
+ setTimeout(() => {
2431
+ if (done) return;
2432
+ done = true;
2433
+ unwatch();
2434
+ reject(new Error("Timed out waiting for block condition"));
2435
+ }, timeoutMs);
2436
+ });
2437
+ };
2438
+
2439
+ exports.computeNullifier = computeNullifier;
2440
+ exports.computeStorageSlot = computeStorageSlot;
2441
+ exports.computeUnspendableAddress = computeUnspendableAddress;
2442
+ exports.depositToZAsset = depositToZAsset;
2443
+ exports.deterministicShuffleClients = deterministicShuffleClients;
2444
+ exports.fetchLightClients = fetchLightClients;
2445
+ exports.fetchMptProof = fetchMptProof;
2446
+ exports.generatePaymentKey = generatePaymentKey;
2447
+ exports.parseProofJson = parseProofJson;
2448
+ exports.proofJsonToRedeemParams = proofJsonToRedeemParams;
2449
+ exports.signAttestedMessage = signAttestedMessage;
2450
+ exports.submitRedeem = submitRedeem;
2451
+ exports.updateLoopbackClient = updateLoopbackClient;