@unionlabs/payments 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/cjs/Attestor.js +9 -2
  2. package/dist/cjs/Attestor.js.map +1 -1
  3. package/dist/cjs/Domain.js +8 -2
  4. package/dist/cjs/Domain.js.map +1 -1
  5. package/dist/cjs/Error.js +1 -1
  6. package/dist/cjs/Error.js.map +1 -1
  7. package/dist/cjs/EvmPublicClient.js +5 -277
  8. package/dist/cjs/EvmPublicClient.js.map +1 -1
  9. package/dist/cjs/EvmWalletClient.js +7 -642
  10. package/dist/cjs/EvmWalletClient.js.map +1 -1
  11. package/dist/cjs/Payment.js +84 -79
  12. package/dist/cjs/Payment.js.map +1 -1
  13. package/dist/cjs/Prover.js +2 -2
  14. package/dist/cjs/Prover.js.map +1 -1
  15. package/dist/cjs/PublicClient.js +2 -2
  16. package/dist/cjs/PublicClient.js.map +1 -1
  17. package/dist/cjs/WalletClient.js +2 -2
  18. package/dist/cjs/WalletClient.js.map +1 -1
  19. package/dist/cjs/constants/ibc-core-registry.js +6 -6
  20. package/dist/cjs/constants/ibc-core-registry.js.map +1 -1
  21. package/dist/cjs/internal/evmPublicClient.js +261 -0
  22. package/dist/cjs/internal/evmPublicClient.js.map +1 -0
  23. package/dist/cjs/internal/evmWalletClient.js +695 -0
  24. package/dist/cjs/internal/evmWalletClient.js.map +1 -1
  25. package/dist/cjs/internal/publicClient.js +0 -2
  26. package/dist/cjs/internal/publicClient.js.map +1 -1
  27. package/dist/cjs/promises/Payment.js +4 -3
  28. package/dist/cjs/promises/Payment.js.map +1 -1
  29. package/dist/cjs/promises/PublicClient.js +1 -2
  30. package/dist/cjs/promises/PublicClient.js.map +1 -1
  31. package/dist/cjs/promises/WalletClient.js +0 -1
  32. package/dist/cjs/promises/WalletClient.js.map +1 -1
  33. package/dist/cjs/rpc.js +3 -5
  34. package/dist/cjs/rpc.js.map +1 -1
  35. package/dist/dts/Attestor.d.ts +21 -6
  36. package/dist/dts/Attestor.d.ts.map +1 -1
  37. package/dist/dts/Domain.d.ts +92 -59
  38. package/dist/dts/Domain.d.ts.map +1 -1
  39. package/dist/dts/Error.d.ts +2 -2
  40. package/dist/dts/Error.d.ts.map +1 -1
  41. package/dist/dts/EvmPublicClient.d.ts +7 -19
  42. package/dist/dts/EvmPublicClient.d.ts.map +1 -1
  43. package/dist/dts/EvmWalletClient.d.ts +7 -23
  44. package/dist/dts/EvmWalletClient.d.ts.map +1 -1
  45. package/dist/dts/Payment.d.ts +71 -69
  46. package/dist/dts/Payment.d.ts.map +1 -1
  47. package/dist/dts/Prover.d.ts +6 -0
  48. package/dist/dts/Prover.d.ts.map +1 -1
  49. package/dist/dts/PublicClient.d.ts +7 -11
  50. package/dist/dts/PublicClient.d.ts.map +1 -1
  51. package/dist/dts/WalletClient.d.ts +10 -4
  52. package/dist/dts/WalletClient.d.ts.map +1 -1
  53. package/dist/dts/constants/ibc-core-registry.d.ts +5 -6
  54. package/dist/dts/constants/ibc-core-registry.d.ts.map +1 -1
  55. package/dist/dts/internal/evm.d.ts +3 -3
  56. package/dist/dts/internal/evm.d.ts.map +1 -1
  57. package/dist/dts/internal/evmPublicClient.d.ts +6 -0
  58. package/dist/dts/internal/evmPublicClient.d.ts.map +1 -0
  59. package/dist/dts/promises/Attestor.d.ts +5 -5
  60. package/dist/dts/promises/EvmPublicClient.d.ts +73 -74
  61. package/dist/dts/promises/EvmPublicClient.d.ts.map +1 -1
  62. package/dist/dts/promises/EvmWalletClient.d.ts +4 -5
  63. package/dist/dts/promises/EvmWalletClient.d.ts.map +1 -1
  64. package/dist/dts/promises/Payment.d.ts +39 -17
  65. package/dist/dts/promises/Payment.d.ts.map +1 -1
  66. package/dist/dts/promises/PublicClient.d.ts +3 -4
  67. package/dist/dts/promises/PublicClient.d.ts.map +1 -1
  68. package/dist/dts/promises/WalletClient.d.ts +77 -34
  69. package/dist/dts/promises/WalletClient.d.ts.map +1 -1
  70. package/dist/dts/rpc.d.ts +31 -0
  71. package/dist/dts/rpc.d.ts.map +1 -1
  72. package/dist/esm/Attestor.js +8 -1
  73. package/dist/esm/Attestor.js.map +1 -1
  74. package/dist/esm/Domain.js +7 -1
  75. package/dist/esm/Domain.js.map +1 -1
  76. package/dist/esm/Error.js +1 -1
  77. package/dist/esm/Error.js.map +1 -1
  78. package/dist/esm/EvmPublicClient.js +4 -275
  79. package/dist/esm/EvmPublicClient.js.map +1 -1
  80. package/dist/esm/EvmWalletClient.js +5 -638
  81. package/dist/esm/EvmWalletClient.js.map +1 -1
  82. package/dist/esm/Payment.js +82 -78
  83. package/dist/esm/Payment.js.map +1 -1
  84. package/dist/esm/Prover.js +1 -1
  85. package/dist/esm/Prover.js.map +1 -1
  86. package/dist/esm/PublicClient.js +2 -2
  87. package/dist/esm/PublicClient.js.map +1 -1
  88. package/dist/esm/Schema.js +8 -4
  89. package/dist/esm/Schema.js.map +1 -1
  90. package/dist/esm/WalletClient.js +2 -2
  91. package/dist/esm/WalletClient.js.map +1 -1
  92. package/dist/esm/constants/ibc-core-registry.js +4 -4
  93. package/dist/esm/constants/ibc-core-registry.js.map +1 -1
  94. package/dist/esm/internal/evmPublicClient.js +253 -0
  95. package/dist/esm/internal/evmPublicClient.js.map +1 -0
  96. package/dist/esm/internal/evmWalletClient.js +691 -1
  97. package/dist/esm/internal/evmWalletClient.js.map +1 -1
  98. package/dist/esm/internal/publicClient.js +0 -2
  99. package/dist/esm/internal/publicClient.js.map +1 -1
  100. package/dist/esm/promises/Payment.js +2 -2
  101. package/dist/esm/promises/Payment.js.map +1 -1
  102. package/dist/esm/promises/PublicClient.js +2 -3
  103. package/dist/esm/promises/PublicClient.js.map +1 -1
  104. package/dist/esm/promises/WalletClient.js +0 -1
  105. package/dist/esm/promises/WalletClient.js.map +1 -1
  106. package/dist/esm/rpc.js +2 -4
  107. package/dist/esm/rpc.js.map +1 -1
  108. package/package.json +7 -3
  109. package/src/Attestor.ts +12 -3
  110. package/src/Domain.ts +30 -9
  111. package/src/Error.ts +1 -0
  112. package/src/EvmPublicClient.ts +9 -488
  113. package/src/EvmWalletClient.ts +21 -973
  114. package/src/Payment.ts +216 -149
  115. package/src/Prover.ts +2 -2
  116. package/src/PublicClient.ts +7 -14
  117. package/src/WalletClient.ts +11 -5
  118. package/src/constants/ibc-core-registry.ts +9 -13
  119. package/src/internal/evmPublicClient.ts +459 -0
  120. package/src/internal/evmWalletClient.ts +1063 -0
  121. package/src/internal/publicClient.ts +0 -3
  122. package/src/promises/Payment.ts +26 -25
  123. package/src/promises/PublicClient.ts +2 -3
  124. package/src/promises/WalletClient.ts +0 -1
  125. package/src/rpc.ts +2 -4
  126. package/unionlabs-payments-0.3.0.tgz +0 -0
@@ -0,0 +1,1063 @@
1
+ import * as A from "effect/Array";
2
+ import * as Effect from "effect/Effect";
3
+ import { pipe } from "effect/Function";
4
+ import * as Number from "effect/Number";
5
+ import * as O from "effect/Option";
6
+ import type * as P from "effect/Predicate";
7
+ import * as R from "effect/Record";
8
+ import * as Ref from "effect/Ref";
9
+ import type * as Scope from "effect/Scope";
10
+ import type * as Mipd from "mipd";
11
+ import type * as Viem from "viem";
12
+ import * as ViemChains from "viem/chains";
13
+ import * as Abi from "../Abi.js";
14
+ import * as ZAssetRegistry from "../constants/z-asset-registry.js";
15
+ import * as Domain from "../Domain.js";
16
+ import * as Error from "../Error.js";
17
+ import type * as EvmWalletClient from "../EvmWalletClient.js";
18
+ import * as Client from "../WalletClient.js";
19
+ import * as internalEvmPublicClient from "./evmPublicClient.js";
20
+
21
+ const UniversalChainIdMap: Record<Domain.UniversalChainId, Viem.Chain> = {
22
+ [Domain.UniversalChainId.make("base.8453")]: ViemChains.base,
23
+ };
24
+
25
+ /** @internal */
26
+ export const resolveUniversalChainId = (
27
+ ucid: Domain.UniversalChainId,
28
+ ): Effect.Effect<Viem.Chain, Error.SdkError, never> =>
29
+ pipe(
30
+ UniversalChainIdMap,
31
+ R.get(ucid),
32
+ O.match({
33
+ onNone: () =>
34
+ Effect.fail(
35
+ new Error.SystemError({
36
+ method: "resolveUniversalChainId",
37
+ module: "EvmWalletClient",
38
+ reason: "InvalidData",
39
+ }),
40
+ ),
41
+ onSome: Effect.succeed,
42
+ }),
43
+ );
44
+
45
+ /** @internal */
46
+ export const TypeId: EvmWalletClient.TypeId = Symbol.for(
47
+ "@unionlabs/payments/EvmWalletClient",
48
+ ) as EvmWalletClient.TypeId;
49
+
50
+ /** @internal */
51
+ export const makeViem = (
52
+ wallet: Viem.WalletClient,
53
+ ): Effect.Effect<
54
+ EvmWalletClient.EvmWalletClient,
55
+ Error.SdkError,
56
+ Scope.Scope
57
+ > =>
58
+ Effect.gen(function* () {
59
+ const Viem = yield* Effect.tryPromise({
60
+ try: () => import("viem").then((x) => x),
61
+ catch: (cause) =>
62
+ new Error.SystemError({
63
+ reason: "DynamicImport",
64
+ method: "makeViem",
65
+ module: "EvmWalletClient",
66
+ cause,
67
+ description: "could not import viem",
68
+ }),
69
+ });
70
+
71
+ const ViemActions = yield* Effect.tryPromise({
72
+ try: () => import("viem/actions").then((x) => x),
73
+ catch: (cause) =>
74
+ new Error.SystemError({
75
+ reason: "DynamicImport",
76
+ method: "makeViemActions",
77
+ module: "EvmWalletClient",
78
+ cause,
79
+ description: "could not import viem/actions",
80
+ }),
81
+ });
82
+
83
+ const publicClient = yield* pipe(
84
+ Effect.try({
85
+ try: () =>
86
+ Viem.createPublicClient({
87
+ chain: wallet.chain,
88
+ transport: Viem.custom(wallet),
89
+ }),
90
+ catch: (cause) =>
91
+ new Error.SystemError({
92
+ reason: "DynamicImport",
93
+ method: "make",
94
+ module: "EvmWalletClient",
95
+ cause,
96
+ description: "could derive public client from wallet client",
97
+ }),
98
+ }),
99
+ Effect.flatMap(internalEvmPublicClient.makeViem),
100
+ );
101
+
102
+ // Use "latest" to avoid stale pending txs in mempool
103
+ const transactionCount = yield* pipe(
104
+ Effect.tryPromise({
105
+ try: () =>
106
+ ViemActions.getTransactionCount(wallet, {
107
+ address: wallet.account?.address!,
108
+ blockTag: "latest",
109
+ }),
110
+ catch: (cause) =>
111
+ new Error.SystemError({
112
+ reason: "InvalidData",
113
+ method: "encodeFunctionData",
114
+ module: "EvmWalletClient",
115
+ cause,
116
+ }),
117
+ }),
118
+ Effect.flatMap(Ref.make),
119
+ );
120
+
121
+ const encodeFunctionData = Effect.fn("encodeFunctionData")(
122
+ (parameters: Viem.EncodeFunctionDataParameters) =>
123
+ Effect.try({
124
+ try: () => Viem.encodeFunctionData(parameters),
125
+ catch: (cause) =>
126
+ new Error.SystemError({
127
+ reason: "InvalidData",
128
+ method: "encodeFunctionData",
129
+ module: "EvmWalletClient",
130
+ cause,
131
+ }),
132
+ }),
133
+ );
134
+
135
+ const validateFunctionData = Effect.fn("validateFunctionData")(
136
+ (parameters: Viem.EncodeFunctionDataParameters) =>
137
+ pipe(
138
+ Effect.try({
139
+ try: () => Viem.encodeFunctionData(parameters),
140
+ catch: (cause) =>
141
+ new Error.SystemError({
142
+ reason: "InvalidData",
143
+ method: "validateFunctionData",
144
+ module: "EvmWalletClient",
145
+ cause,
146
+ }),
147
+ }),
148
+ Effect.andThen(
149
+ () =>
150
+ ({
151
+ ...parameters,
152
+ args: parameters.args ?? [],
153
+ functionName: parameters.functionName!,
154
+ }) as const,
155
+ ),
156
+ ),
157
+ );
158
+
159
+ const writeContract = Effect.fn("writeContract")(
160
+ <
161
+ const abi extends Viem.Abi | readonly unknown[],
162
+ functionName extends Viem.ContractFunctionName<
163
+ abi,
164
+ "payable" | "nonpayable"
165
+ >,
166
+ args extends Viem.ContractFunctionArgs<
167
+ abi,
168
+ "payable" | "nonpayable",
169
+ functionName
170
+ >,
171
+ >(
172
+ params: Viem.WriteContractParameters<abi, functionName, args>,
173
+ ) =>
174
+ pipe(
175
+ Effect.tryPromise({
176
+ try: () => wallet.writeContract(params),
177
+ catch: (cause) =>
178
+ new Error.SystemError({
179
+ method: "writeContract",
180
+ module: "EvmWalletClient",
181
+ reason: "InvalidData",
182
+ cause,
183
+ }),
184
+ }),
185
+ Effect.map((hash) =>
186
+ Domain.SubmissionResult.SubmissionEvm({
187
+ hash: Domain.TxHash(hash),
188
+ }),
189
+ ),
190
+ ),
191
+ );
192
+
193
+ const prepareTransactionRequest = Effect.fn("prepareTransactionRequest")(
194
+ (params: Viem.PrepareTransactionRequestParameters) =>
195
+ pipe(
196
+ Ref.get(transactionCount),
197
+ Effect.flatMap((nonce) =>
198
+ Effect.tryPromise({
199
+ try: () =>
200
+ wallet.prepareTransactionRequest({
201
+ ...params,
202
+ nonce,
203
+ }),
204
+ catch: (cause) =>
205
+ new Error.SystemError({
206
+ method: "prepareTransactionRequest",
207
+ module: "EvmWalletClient",
208
+ reason: "InvalidData",
209
+ cause,
210
+ }),
211
+ }),
212
+ ),
213
+ Effect.tap(() => Ref.update(transactionCount, Number.increment)),
214
+ ),
215
+ );
216
+
217
+ const prepareBatchTransactionRequest = Effect.fn(
218
+ "prepareBatchTransactionRequest",
219
+ )((requests: ReadonlyArray<Domain.PreparedRequest>) =>
220
+ Effect.gen(function* () {
221
+ // Refresh nonce from chain before batch to ensure we're in sync
222
+ const chainNonce = yield* Effect.tryPromise({
223
+ try: () =>
224
+ ViemActions.getTransactionCount(wallet, {
225
+ address: wallet.account?.address!,
226
+ blockTag: "latest",
227
+ }),
228
+ catch: (cause) =>
229
+ new Error.SystemError({
230
+ reason: "InvalidData",
231
+ method: "prepareBatchTransactionRequest",
232
+ module: "EvmWalletClient",
233
+ cause,
234
+ }),
235
+ });
236
+ yield* Ref.set(transactionCount, chainNonce);
237
+ yield* Effect.log("[prepareBatchTransactionRequest] Synced nonce", {
238
+ chainNonce,
239
+ });
240
+
241
+ const calls = yield* pipe(
242
+ requests,
243
+ Effect.forEach((request) =>
244
+ pipe(
245
+ encodeFunctionData({
246
+ abi: request.abi,
247
+ args: request.args,
248
+ functionName: request.functionName,
249
+ }),
250
+ Effect.map(
251
+ (data) =>
252
+ ({
253
+ to: request.contractAddress,
254
+ data,
255
+ value: undefined,
256
+ }) as const,
257
+ ),
258
+ ),
259
+ ),
260
+ );
261
+
262
+ yield* Effect.log({ calls });
263
+
264
+ const simulationResult = yield* publicClient.simulateCalls({
265
+ calls: calls.map((call) => ({
266
+ to: call.to,
267
+ data: call.data,
268
+ value: call.value,
269
+ })),
270
+ account: wallet.account?.address!,
271
+ });
272
+
273
+ yield* Effect.log(
274
+ "[prepareBatchTransactionRequest] Simulation results",
275
+ {
276
+ results: simulationResult.results,
277
+ },
278
+ );
279
+
280
+ // Extract gas estimates with 20% buffer for safety
281
+ const gasEstimates: Array<bigint | undefined> =
282
+ simulationResult.results?.map(
283
+ (r: { gasUsed?: bigint | number | string }) =>
284
+ r?.gasUsed ? (BigInt(r.gasUsed) * 120n) / 100n : undefined,
285
+ ) ?? [];
286
+
287
+ yield* Effect.log("[prepareBatchTransactionRequest] Gas estimates", {
288
+ gasEstimates,
289
+ });
290
+
291
+ // Prepare each transaction with gas
292
+ const prepared: Array<Viem.PrepareTransactionRequestReturnType> = [];
293
+ for (let i = 0; i < calls.length; i++) {
294
+ const call = calls[i];
295
+ const gas = gasEstimates[i];
296
+ const nonce = yield* Ref.get(transactionCount);
297
+
298
+ const request = yield* Effect.tryPromise({
299
+ try: () =>
300
+ wallet.prepareTransactionRequest({
301
+ to: call.to,
302
+ data: call.data,
303
+ ...(call.value !== undefined && { value: call.value }),
304
+ ...(gas !== undefined && { gas }),
305
+ nonce,
306
+ chain: wallet.chain,
307
+ }),
308
+ catch: (cause) =>
309
+ new Error.SystemError({
310
+ method: "prepareBatchTransactionRequest",
311
+ module: "EvmWalletClient",
312
+ reason: "InvalidData",
313
+ cause,
314
+ }),
315
+ });
316
+
317
+ yield* Ref.update(transactionCount, Number.increment);
318
+ prepared.push(request);
319
+ }
320
+
321
+ return prepared;
322
+ }),
323
+ );
324
+
325
+ const signTransaction = Effect.fn("signTransaction")(
326
+ (params: Viem.SignTransactionParameters<undefined, undefined>) =>
327
+ Effect.tryPromise({
328
+ try: () => wallet.signTransaction(params),
329
+ catch: (cause) =>
330
+ new Error.SystemError({
331
+ method: "signTransaction",
332
+ module: "EvmWalletClient",
333
+ reason: "InvalidData",
334
+ cause,
335
+ }),
336
+ }),
337
+ );
338
+
339
+ const sendRawTransaction = Effect.fn("sendRawTransaction")(
340
+ (params: Viem.SendRawTransactionParameters) =>
341
+ Effect.tryPromise({
342
+ try: () => wallet.sendRawTransaction(params),
343
+ catch: (cause) =>
344
+ new Error.SystemError({
345
+ method: "sendRawTransaction",
346
+ module: "EvmWalletClient",
347
+ reason: "InvalidData",
348
+ cause,
349
+ }),
350
+ }),
351
+ );
352
+
353
+ const sign = Effect.fn("sign")(function* (
354
+ request: ReadonlyArray<Domain.PreparedRequest> | Domain.PreparedRequest,
355
+ ) {
356
+ // const chain = yield* resolveUniversalChainId(request.universalChainId);
357
+ const requests = yield* pipe(
358
+ request,
359
+ A.ensure,
360
+ Effect.forEach((n) =>
361
+ pipe(
362
+ n,
363
+ Effect.liftPredicate(
364
+ Domain.PreparedRequest.$is("PreparedEvm"),
365
+ (x) =>
366
+ new Error.SystemError({
367
+ method: "sign",
368
+ module: "EvmWalletClient",
369
+ reason: "InvalidData",
370
+ }),
371
+ ),
372
+ ),
373
+ ),
374
+ );
375
+
376
+ return yield* pipe(
377
+ requests,
378
+ prepareBatchTransactionRequest,
379
+ Effect.flatMap(
380
+ Effect.forEach((value) =>
381
+ signTransaction({
382
+ ...value,
383
+ account: wallet.account!,
384
+ chain: wallet.chain!,
385
+ }),
386
+ ),
387
+ ),
388
+ // Effect.tap((success) => Effect.log({ success })),
389
+ Effect.map(A.map((value) => Domain.SignedRequest.SignedEvm({ value }))),
390
+ );
391
+ });
392
+
393
+ const submit = Effect.fn("submit")((request: Domain.SignedRequest) =>
394
+ pipe(
395
+ request,
396
+ Effect.liftPredicate(
397
+ Domain.SignedRequest.$is("SignedEvm"),
398
+ (cause) =>
399
+ new Error.SystemError({
400
+ method: "submit",
401
+ module: "EvmWalletClient",
402
+ reason: "InvalidData",
403
+ cause,
404
+ }),
405
+ ),
406
+ Effect.map((x) => x.value),
407
+ Effect.flatMap((serializedTransaction) =>
408
+ sendRawTransaction({ serializedTransaction }),
409
+ ),
410
+ Effect.map((hash) =>
411
+ Domain.SubmissionResult.SubmissionEvm({
412
+ hash: Domain.TxHash(hash),
413
+ }),
414
+ ),
415
+ ),
416
+ );
417
+
418
+ const signAndSubmit = Effect.fn("signAndSubmit")(function* (
419
+ request: Domain.PreparedRequest,
420
+ ) {
421
+ const _request = yield* Effect.liftPredicate(
422
+ request,
423
+ Domain.PreparedRequest.$is("PreparedEvm"),
424
+ (cause) =>
425
+ new Error.SystemError({
426
+ method: "signAndSubmit",
427
+ module: "EvmWalletClient",
428
+ reason: "InvalidData",
429
+ cause,
430
+ }),
431
+ );
432
+
433
+ const data = yield* encodeFunctionData({
434
+ abi: _request.abi,
435
+ args: _request.args,
436
+ functionName: _request.functionName,
437
+ });
438
+
439
+ const prepared = yield* prepareTransactionRequest({
440
+ to: _request.contractAddress,
441
+ data,
442
+ chain: wallet.chain,
443
+ });
444
+
445
+ return yield* pipe(
446
+ Effect.tryPromise({
447
+ try: () =>
448
+ ViemActions.sendTransaction(wallet, {
449
+ to: prepared.to,
450
+ data: prepared.data,
451
+ value: prepared.value,
452
+ gas: prepared.gas,
453
+ nonce: prepared.nonce,
454
+ account: wallet.account!,
455
+ chain: wallet.chain!,
456
+ }),
457
+ catch: (cause) =>
458
+ new Error.SystemError({
459
+ method: "signAndSubmit",
460
+ module: "EvmWalletClient",
461
+ reason: "InvalidData",
462
+ cause,
463
+ }),
464
+ }),
465
+ Effect.map((hash) =>
466
+ Domain.SubmissionResult.SubmissionEvm({
467
+ hash: Domain.TxHash(hash),
468
+ }),
469
+ ),
470
+ );
471
+ });
472
+
473
+ // TODO: check if required
474
+ const approveZAssetToSpendErc20 = Effect.fn("approveZAssetToSpendErc20")(
475
+ function* (args: Client.WalletClient.ApproveZAssetToSpendErc20) {
476
+ return yield* pipe(
477
+ // XXX: fix hardcoded chainid
478
+ ZAssetRegistry.getZAsset(8453n, args.srcErc20Address),
479
+ Effect.mapError(
480
+ (cause) =>
481
+ new Error.SystemError({
482
+ method: "approveZAssetToSpendErc20",
483
+ module: "EvmWalletClient",
484
+ reason: "InvalidData",
485
+ cause,
486
+ }),
487
+ ),
488
+ Effect.flatMap((zAsset) =>
489
+ validateFunctionData({
490
+ abi: Abi.ERC20_ABI,
491
+ functionName: "approve",
492
+ args: [zAsset, args.amount],
493
+ }),
494
+ ),
495
+ Effect.map((request) =>
496
+ Domain.PreparedRequest.PreparedEvm({
497
+ abi: request.abi,
498
+ args: request.args ?? [],
499
+ contractAddress: args.srcErc20Address,
500
+ functionName: request.functionName || "",
501
+ universalChainId: args.universalChainId,
502
+ kind: "Erc20.Approve",
503
+ }),
504
+ ),
505
+ );
506
+ },
507
+ );
508
+
509
+ // TODO: check if required
510
+ const updateLoopbackClient = Effect.fn("updateLoopbackClient")(function* ({
511
+ clientId,
512
+ height,
513
+ ibcHandlerAddress,
514
+ universalChainId,
515
+ }: Client.WalletClient.UpdateLoopbackClient) {
516
+ // const publicClient = Viem.createPublicClient({
517
+ // transport: Viem.http(wallet.transport.rpcUrl),
518
+ // });
519
+
520
+ const publicClient = wallet.extend(Viem.publicActions);
521
+
522
+ const blockNumber = height;
523
+
524
+ const block = yield* Effect.tryPromise({
525
+ try: () => publicClient.getBlock({ blockNumber }),
526
+ catch: (cause) =>
527
+ new Error.SystemError({
528
+ module: "EvmWalletClient",
529
+ method: "updateLoopbackClient",
530
+ reason: "InvalidData",
531
+ description: "Cannot getBlockNumber",
532
+ cause,
533
+ }),
534
+ });
535
+
536
+ // if (!block.number) {
537
+ // throw new Error("Block number is null");
538
+ // }
539
+
540
+ // Helper to convert bigint/number to minimal RLP hex encoding
541
+ const toRlpHex = (
542
+ value: bigint | number | null | undefined,
543
+ ): Viem.Hex => {
544
+ if (
545
+ value === undefined ||
546
+ value === null ||
547
+ value === 0n ||
548
+ value === 0
549
+ ) {
550
+ return "0x" as Viem.Hex;
551
+ }
552
+ let hex =
553
+ typeof value === "bigint" ? value.toString(16) : value.toString(16);
554
+ if (hex.length % 2 !== 0) {
555
+ hex = "0" + hex;
556
+ }
557
+ return `0x${hex}` as Viem.Hex;
558
+ };
559
+
560
+ // Build header fields in order (pre-merge + post-merge fields)
561
+ const headerFields: (Viem.Hex | Viem.Hex[])[] = [
562
+ block.parentHash,
563
+ block.sha3Uncles,
564
+ block.miner,
565
+ block.stateRoot,
566
+ block.transactionsRoot,
567
+ block.receiptsRoot,
568
+ block.logsBloom ?? (("0x" + "00".repeat(256)) as Viem.Hex),
569
+ toRlpHex(block.difficulty),
570
+ toRlpHex(block.number),
571
+ toRlpHex(block.gasLimit),
572
+ toRlpHex(block.gasUsed),
573
+ toRlpHex(block.timestamp),
574
+ block.extraData,
575
+ block.mixHash ??
576
+ ("0x0000000000000000000000000000000000000000000000000000000000000000" as Viem.Hex),
577
+ block.nonce ?? ("0x0000000000000000" as Viem.Hex),
578
+ ];
579
+
580
+ // Post-merge fields
581
+ if (block.baseFeePerGas !== undefined && block.baseFeePerGas !== null) {
582
+ headerFields.push(toRlpHex(block.baseFeePerGas));
583
+ }
584
+ if (block.withdrawalsRoot) {
585
+ headerFields.push(block.withdrawalsRoot);
586
+ }
587
+ if (block.blobGasUsed !== undefined && block.blobGasUsed !== null) {
588
+ headerFields.push(toRlpHex(block.blobGasUsed));
589
+ }
590
+ if (block.excessBlobGas !== undefined && block.excessBlobGas !== null) {
591
+ headerFields.push(toRlpHex(block.excessBlobGas));
592
+ }
593
+ if (block.parentBeaconBlockRoot) {
594
+ headerFields.push(block.parentBeaconBlockRoot);
595
+ }
596
+ const blockAny = block as Record<string, unknown>;
597
+ if (blockAny.requestsHash) {
598
+ headerFields.push(blockAny.requestsHash as Viem.Hex);
599
+ }
600
+
601
+ const rlpEncoded = Viem.toRlp(headerFields);
602
+
603
+ // Verify the encoding produces the correct block hash
604
+ const computedHash = Viem.keccak256(rlpEncoded);
605
+ if (computedHash !== block.hash) {
606
+ return yield* new Error.SystemError({
607
+ module: "EvmWalletClient",
608
+ method: "updateLooopbackClient",
609
+ reason: "InvalidData",
610
+ description: `RLP encoding mismatch: computed hash ${computedHash} does not match block hash ${block.hash}`,
611
+ });
612
+ }
613
+
614
+ // Encode the Header struct: (uint64 height, bytes encodedHeader)
615
+ const clientMessage = Viem.encodeAbiParameters(
616
+ [
617
+ { type: "uint64", name: "height" },
618
+ { type: "bytes", name: "encodedHeader" },
619
+ ],
620
+ [block.number, rlpEncoded],
621
+ );
622
+
623
+ const LIGHTCLIENT_ABI = [
624
+ {
625
+ inputs: [
626
+ { name: "caller", type: "address" },
627
+ { name: "clientId", type: "uint32" },
628
+ { name: "clientMessage", type: "bytes" },
629
+ { name: "relayer", type: "address" },
630
+ ],
631
+ name: "updateClient",
632
+ outputs: [],
633
+ stateMutability: "nonpayable",
634
+ type: "function",
635
+ },
636
+ ] as const;
637
+
638
+ // Wait for the next block so the fetched block's hash is verifiable on-chain
639
+ let currentBlock = yield* Effect.tryPromise({
640
+ try: () => publicClient.getBlockNumber(),
641
+ catch: (cause) =>
642
+ new Error.SystemError({
643
+ module: "EvmWalletClient",
644
+ method: "updateLoopbackClient",
645
+ reason: "InvalidData",
646
+ description: "Cannot getBlockNumber",
647
+ cause,
648
+ }),
649
+ });
650
+
651
+ while (currentBlock <= blockNumber) {
652
+ yield* Effect.sleep("1 second");
653
+ currentBlock = yield* Effect.tryPromise({
654
+ try: () => publicClient.getBlockNumber(),
655
+ catch: (cause) =>
656
+ new Error.SystemError({
657
+ module: "EvmWalletClient",
658
+ method: "updateLoopbackClient",
659
+ reason: "InvalidData",
660
+ description: "Cannot getBlockNumber",
661
+ cause,
662
+ }),
663
+ });
664
+ }
665
+
666
+ const loopbackClient = yield* pipe(
667
+ Effect.tryPromise({
668
+ try: () =>
669
+ publicClient.readContract({
670
+ address: ibcHandlerAddress,
671
+ abi: Abi.IBC_STORE_ABI,
672
+ functionName: "getClient",
673
+ args: [clientId],
674
+ }),
675
+ catch: (cause) =>
676
+ new Error.SystemError({
677
+ module: "EvmWalletClient",
678
+ method: "updateLoopbackClient",
679
+ reason: "InvalidData",
680
+ description: "cannot get client given ibc handler address",
681
+ cause,
682
+ }),
683
+ }),
684
+ Effect.map(Domain.Erc20Address),
685
+ );
686
+
687
+ return yield* pipe(
688
+ validateFunctionData({
689
+ abi: LIGHTCLIENT_ABI,
690
+ functionName: "updateClient",
691
+ args: [
692
+ wallet.account!.address,
693
+ clientId,
694
+ clientMessage,
695
+ wallet.account!.address,
696
+ ],
697
+ }),
698
+ Effect.map((data) =>
699
+ Domain.PreparedRequest.PreparedEvm({
700
+ ...data,
701
+ contractAddress: loopbackClient,
702
+ universalChainId,
703
+ kind: "LoopbackClient.Update",
704
+ }),
705
+ ),
706
+ );
707
+ });
708
+
709
+ const depositUnderlyingZAsset = Effect.fn("depositUnderlyingZAsset")(
710
+ function* (args: Client.WalletClient.DepositUnderlyingZAsset) {
711
+ const zAssetAddr = yield* ZAssetRegistry.getZAsset(
712
+ 8453n,
713
+ args.srcErc20Address,
714
+ ).pipe(
715
+ Effect.mapError(
716
+ (cause) =>
717
+ new Error.SystemError({
718
+ method: "depositUnderlyingZAsset",
719
+ module: "EvmWalletClient",
720
+ reason: "InvalidData",
721
+ cause,
722
+ }),
723
+ ),
724
+ );
725
+
726
+ return yield* pipe(
727
+ validateFunctionData({
728
+ abi: Abi.ZASSET_ABI,
729
+ functionName: "deposit",
730
+ args: [args.amount],
731
+ }),
732
+ // Effect.map((data) =>
733
+ // prepareTransactionRequest({
734
+ // to: zAssetAddr,
735
+ // data,
736
+ // chain: wallet.chain,
737
+ // }),
738
+ // ),
739
+ Effect.map((data) =>
740
+ Domain.PreparedRequest.PreparedEvm({
741
+ ...data,
742
+ contractAddress: Domain.Erc20Address(zAssetAddr),
743
+ universalChainId: args.universalChainId,
744
+ kind: "Erc20.Wrap",
745
+ }),
746
+ ),
747
+ );
748
+ },
749
+ );
750
+
751
+ const transferZAsset = Effect.fn("transferZAsset")(function* (
752
+ args: Client.WalletClient.TransferZAsset,
753
+ ) {
754
+ const zAssetAddr = yield* ZAssetRegistry.getZAsset(
755
+ 8453n,
756
+ args.srcErc20Address,
757
+ ).pipe(
758
+ Effect.mapError(
759
+ (cause) =>
760
+ new Error.SystemError({
761
+ method: "transferZAsset",
762
+ module: "EvmWalletClient",
763
+ reason: "InvalidData",
764
+ cause,
765
+ }),
766
+ ),
767
+ );
768
+
769
+ return yield* pipe(
770
+ validateFunctionData({
771
+ abi: Abi.ZASSET_ABI,
772
+ functionName: "transfer",
773
+ args: [args.depositAddress, args.amount],
774
+ }),
775
+ // Effect.flatMap((data) =>
776
+ // prepareTransactionRequest({
777
+ // to: zAssetAddr,
778
+ // data,
779
+ // chain: wallet.chain,
780
+ // }),
781
+ // ),
782
+ Effect.map((data) =>
783
+ Domain.PreparedRequest.PreparedEvm({
784
+ ...data,
785
+ contractAddress: Domain.Erc20Address(zAssetAddr),
786
+ universalChainId: args.universalChainId,
787
+ kind: "ZAsset.Transfer",
788
+ }),
789
+ ),
790
+ );
791
+ });
792
+
793
+ const prepareDeposit = Effect.fn("prepareDeposit")(function* (
794
+ args: Client.WalletClient.Deposit,
795
+ ) {
796
+ const zAssetAddress =
797
+ ZAssetRegistry.Z_ASSET_REGISTRY[`${args.sourceChainId}`][
798
+ args.srcErc20Address
799
+ ];
800
+
801
+ if (!zAssetAddress) {
802
+ return yield* new Error.SystemError({
803
+ method: "deposit",
804
+ module: "PaymentClient",
805
+ reason: "InvalidData",
806
+ description: `No zAsset found for ${args.srcErc20Address} on chain ${args.sourceChainId}`,
807
+ });
808
+ }
809
+
810
+ const [approval, deposit, transfer] = yield* Effect.all(
811
+ [
812
+ pipe(
813
+ validateFunctionData({
814
+ abi: Abi.ERC20_ABI,
815
+ functionName: "approve",
816
+ args: [zAssetAddress, args.amount],
817
+ }),
818
+ Effect.map((data) =>
819
+ Domain.PreparedRequest.PreparedEvm({
820
+ ...data,
821
+ contractAddress: args.srcErc20Address,
822
+ universalChainId: args.universalChainId,
823
+ kind: "Erc20.Approve",
824
+ }),
825
+ ),
826
+ ),
827
+ pipe(
828
+ validateFunctionData({
829
+ abi: Abi.ZASSET_ABI,
830
+ functionName: "deposit",
831
+ args: [args.amount],
832
+ }),
833
+ Effect.map((data) =>
834
+ Domain.PreparedRequest.PreparedEvm({
835
+ ...data,
836
+ contractAddress: Domain.Erc20Address(zAssetAddress),
837
+ universalChainId: args.universalChainId,
838
+ kind: "Erc20.Wrap",
839
+ }),
840
+ ),
841
+ ),
842
+ pipe(
843
+ validateFunctionData({
844
+ abi: Abi.ZASSET_ABI,
845
+ functionName: "transfer",
846
+ args: [args.depositAddress, args.amount],
847
+ }),
848
+ Effect.map((data) =>
849
+ Domain.PreparedRequest.PreparedEvm({
850
+ ...data,
851
+ contractAddress: Domain.Erc20Address(zAssetAddress),
852
+ universalChainId: args.universalChainId,
853
+ kind: "ZAsset.Transfer",
854
+ }),
855
+ ),
856
+ ),
857
+ ] as const,
858
+ { concurrency: "unbounded" },
859
+ );
860
+
861
+ yield* Effect.log("[deposit]", { approval, deposit, transfer });
862
+
863
+ return <Domain.PreparedRequests>[approval, deposit, transfer];
864
+ });
865
+
866
+ const prepareRedemption = Effect.fn("prepareRedemption")(function* (
867
+ args: Client.WalletClient.Redeem,
868
+ ) {
869
+ const zAssetAddr = yield* ZAssetRegistry.getZAsset(
870
+ 8453n,
871
+ args.dstErc20Address,
872
+ ).pipe(
873
+ Effect.mapError(
874
+ (cause) =>
875
+ new Error.SystemError({
876
+ method: "transferZAsset",
877
+ module: "EvmWalletClient",
878
+ reason: "InvalidData",
879
+ cause,
880
+ }),
881
+ ),
882
+ );
883
+ return yield* pipe(
884
+ validateFunctionData({
885
+ abi: Abi.ZASSET_ABI,
886
+ functionName: "redeem",
887
+ args: [
888
+ args.proof,
889
+ args.commitments,
890
+ args.commitmentPok,
891
+ args.lightClients,
892
+ args.nullifier,
893
+ args.value,
894
+ args.beneficiary,
895
+ args.attestedMessage,
896
+ args.signature,
897
+ args.unwrap ?? true,
898
+ ],
899
+ }),
900
+ // Effect.flatMap((data) =>
901
+ // prepareTransactionRequest({
902
+ // to: zAssetAddr,
903
+ // data,
904
+ // chain: wallet.chain,
905
+ // }),
906
+ // ),
907
+ Effect.map((data) =>
908
+ Domain.PreparedRequest.PreparedEvm({
909
+ ...data,
910
+ contractAddress: Domain.Erc20Address(zAssetAddr),
911
+ universalChainId: args.universalChainId,
912
+ kind: "ZAsset.Transfer",
913
+ }),
914
+ ),
915
+ );
916
+ });
917
+
918
+ return Object.assign(
919
+ yield* Client.make({
920
+ sign: sign,
921
+ submit,
922
+ approveZAssetToSpendErc20,
923
+ updateLoopbackClient,
924
+ depositUnderlyingZAsset,
925
+ transferZAsset,
926
+ prepareDeposit,
927
+ prepareRedemption,
928
+ signAndSubmit,
929
+ }),
930
+ {
931
+ [TypeId]: TypeId as EvmWalletClient.TypeId,
932
+ signTransaction,
933
+ sendRawTransaction,
934
+ writeContract,
935
+ prepareBatchTransactionRequest,
936
+ },
937
+ );
938
+ });
939
+
940
+ /** @internal */
941
+ export const makeBrowser = Effect.fn(function* (options: {
942
+ providerPredicate: P.Predicate<Mipd.EIP6963ProviderDetail>;
943
+ accountPredicate: P.Predicate<`0x${string}`>;
944
+ }) {
945
+ const Mipd = yield* Effect.tryPromise({
946
+ try: () => import("mipd").then((x) => x),
947
+ catch: (cause) =>
948
+ new Error.SystemError({
949
+ reason: "DynamicImport",
950
+ method: "makeBrowser",
951
+ module: "EvmWalletClient",
952
+ cause,
953
+ description: "could not import mipd",
954
+ }),
955
+ });
956
+
957
+ const store = Mipd.createStore();
958
+ yield* Effect.log({ store });
959
+
960
+ const providers = store.getProviders();
961
+ yield* Effect.log({ providers });
962
+
963
+ const selected = yield* pipe(
964
+ A.findFirst(providers, options.providerPredicate),
965
+ Effect.mapError(
966
+ (cause) =>
967
+ new Error.SystemError({
968
+ reason: "DynamicImport",
969
+ method: "makeBrowser",
970
+ module: "EvmWalletClient",
971
+ cause,
972
+ description: "could not find provider",
973
+ }),
974
+ ),
975
+ );
976
+ yield* Effect.log({ selected });
977
+
978
+ const account = yield* pipe(
979
+ Effect.tryPromise({
980
+ try: () => selected.provider.request({ method: "eth_requestAccounts" }),
981
+ catch: (cause) =>
982
+ new Error.SystemError({
983
+ reason: "DynamicImport",
984
+ method: "makeBrowser",
985
+ module: "EvmWalletClient",
986
+ cause,
987
+ description: "could not request accounts",
988
+ }),
989
+ }),
990
+ Effect.flatMap(A.findFirst(options.accountPredicate)),
991
+ Effect.catchTag(
992
+ "NoSuchElementException",
993
+ (cause) =>
994
+ new Error.SystemError({
995
+ reason: "DynamicImport",
996
+ method: "makeBrowser",
997
+ module: "EvmWalletClient",
998
+ cause,
999
+ description: "could not match account by predicate",
1000
+ }),
1001
+ ),
1002
+ );
1003
+
1004
+ const Viem = yield* Effect.tryPromise({
1005
+ try: () => import("viem").then((x) => x),
1006
+ catch: (cause) =>
1007
+ new Error.SystemError({
1008
+ reason: "DynamicImport",
1009
+ method: "makeViem",
1010
+ module: "EvmWalletClient",
1011
+ cause,
1012
+ description: "could not import viem",
1013
+ }),
1014
+ });
1015
+
1016
+ const ViemChains = yield* Effect.tryPromise({
1017
+ try: () => import("viem/chains").then((x) => x),
1018
+ catch: (cause) =>
1019
+ new Error.SystemError({
1020
+ reason: "DynamicImport",
1021
+ method: "makeViem",
1022
+ module: "EvmWalletClient",
1023
+ cause,
1024
+ description: "could not import viem/chains",
1025
+ }),
1026
+ });
1027
+
1028
+ const wallet = yield* Effect.try({
1029
+ try: () =>
1030
+ Viem.createWalletClient({
1031
+ account: account,
1032
+ chain: ViemChains.base,
1033
+ transport: Viem.custom(selected.provider),
1034
+ }),
1035
+ catch: (cause) =>
1036
+ new Error.SystemError({
1037
+ reason: "DynamicImport",
1038
+ method: "makeViem",
1039
+ module: "EvmWalletClient",
1040
+ cause,
1041
+ description: "could not import viem/chains",
1042
+ }),
1043
+ });
1044
+
1045
+ return yield* makeViem(wallet);
1046
+ });
1047
+
1048
+ // import type * as ThirdwebAdaptersViem from "thirdweb/adapters/viem"
1049
+ // export const makeThirdweb = Effect.fn(function* (...options: Parameters<typeof ThirdwebAdaptersViem['viemAdapter']['wallet']['toViem']>) {
1050
+ // const { viemAdapter } = yield* Effect.tryPromise({
1051
+ // try: () => import("thirdweb/adapters/viem"),
1052
+ // catch: (cause) => new Error.SystemError({
1053
+ // method: "makeThirdweb",
1054
+ // module: "EvmWalletClient",
1055
+ // reason: "DynamicImport",
1056
+ // cause,
1057
+ // })
1058
+ // })
1059
+
1060
+ // const wallet = viemAdapter.wallet.toViem(...options)
1061
+
1062
+ // return layerFromViem(wallet as Viem.WalletClient)
1063
+ // })