@unionlabs/payments 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/ChainRegistry/package.json +6 -0
  2. package/README.md +283 -1
  3. package/dist/cjs/Attestor.js +9 -2
  4. package/dist/cjs/Attestor.js.map +1 -1
  5. package/dist/cjs/ChainRegistry.js +24 -0
  6. package/dist/cjs/ChainRegistry.js.map +1 -0
  7. package/dist/cjs/Domain.js +8 -2
  8. package/dist/cjs/Domain.js.map +1 -1
  9. package/dist/cjs/Error.js +1 -1
  10. package/dist/cjs/Error.js.map +1 -1
  11. package/dist/cjs/EvmPublicClient.js +5 -277
  12. package/dist/cjs/EvmPublicClient.js.map +1 -1
  13. package/dist/cjs/EvmWalletClient.js +7 -642
  14. package/dist/cjs/EvmWalletClient.js.map +1 -1
  15. package/dist/cjs/Payment.js +86 -79
  16. package/dist/cjs/Payment.js.map +1 -1
  17. package/dist/cjs/Prover.js +2 -2
  18. package/dist/cjs/Prover.js.map +1 -1
  19. package/dist/cjs/PublicClient.js +2 -2
  20. package/dist/cjs/PublicClient.js.map +1 -1
  21. package/dist/cjs/WalletClient.js +2 -2
  22. package/dist/cjs/WalletClient.js.map +1 -1
  23. package/dist/cjs/constants/ibc-core-registry.js +7 -9
  24. package/dist/cjs/constants/ibc-core-registry.js.map +1 -1
  25. package/dist/cjs/constants/z-asset-registry.js +9 -7
  26. package/dist/cjs/constants/z-asset-registry.js.map +1 -1
  27. package/dist/cjs/internal/evmPublicClient.js +261 -0
  28. package/dist/cjs/internal/evmPublicClient.js.map +1 -0
  29. package/dist/cjs/internal/evmWalletClient.js +650 -0
  30. package/dist/cjs/internal/evmWalletClient.js.map +1 -1
  31. package/dist/cjs/internal/publicClient.js +0 -2
  32. package/dist/cjs/internal/publicClient.js.map +1 -1
  33. package/dist/cjs/promises/Payment.js +4 -3
  34. package/dist/cjs/promises/Payment.js.map +1 -1
  35. package/dist/cjs/promises/PublicClient.js +1 -2
  36. package/dist/cjs/promises/PublicClient.js.map +1 -1
  37. package/dist/cjs/promises/WalletClient.js +0 -1
  38. package/dist/cjs/promises/WalletClient.js.map +1 -1
  39. package/dist/cjs/rpc.js +3 -5
  40. package/dist/cjs/rpc.js.map +1 -1
  41. package/dist/dts/Attestor.d.ts +21 -6
  42. package/dist/dts/Attestor.d.ts.map +1 -1
  43. package/dist/dts/ChainRegistry.d.ts +6 -0
  44. package/dist/dts/ChainRegistry.d.ts.map +1 -0
  45. package/dist/dts/Domain.d.ts +92 -59
  46. package/dist/dts/Domain.d.ts.map +1 -1
  47. package/dist/dts/Error.d.ts +2 -2
  48. package/dist/dts/Error.d.ts.map +1 -1
  49. package/dist/dts/EvmPublicClient.d.ts +7 -19
  50. package/dist/dts/EvmPublicClient.d.ts.map +1 -1
  51. package/dist/dts/EvmWalletClient.d.ts +7 -23
  52. package/dist/dts/EvmWalletClient.d.ts.map +1 -1
  53. package/dist/dts/Payment.d.ts +72 -71
  54. package/dist/dts/Payment.d.ts.map +1 -1
  55. package/dist/dts/Prover.d.ts +6 -0
  56. package/dist/dts/Prover.d.ts.map +1 -1
  57. package/dist/dts/PublicClient.d.ts +11 -15
  58. package/dist/dts/PublicClient.d.ts.map +1 -1
  59. package/dist/dts/WalletClient.d.ts +10 -6
  60. package/dist/dts/WalletClient.d.ts.map +1 -1
  61. package/dist/dts/constants/ibc-core-registry.d.ts +5 -6
  62. package/dist/dts/constants/ibc-core-registry.d.ts.map +1 -1
  63. package/dist/dts/constants/z-asset-registry.d.ts +4 -4
  64. package/dist/dts/constants/z-asset-registry.d.ts.map +1 -1
  65. package/dist/dts/internal/evm.d.ts +3 -3
  66. package/dist/dts/internal/evm.d.ts.map +1 -1
  67. package/dist/dts/internal/evmPublicClient.d.ts +6 -0
  68. package/dist/dts/internal/evmPublicClient.d.ts.map +1 -0
  69. package/dist/dts/promises/Attestor.d.ts +5 -5
  70. package/dist/dts/promises/EvmPublicClient.d.ts +73 -74
  71. package/dist/dts/promises/EvmPublicClient.d.ts.map +1 -1
  72. package/dist/dts/promises/EvmWalletClient.d.ts +4 -5
  73. package/dist/dts/promises/EvmWalletClient.d.ts.map +1 -1
  74. package/dist/dts/promises/Payment.d.ts +39 -17
  75. package/dist/dts/promises/Payment.d.ts.map +1 -1
  76. package/dist/dts/promises/PublicClient.d.ts +3 -4
  77. package/dist/dts/promises/PublicClient.d.ts.map +1 -1
  78. package/dist/dts/promises/WalletClient.d.ts +77 -34
  79. package/dist/dts/promises/WalletClient.d.ts.map +1 -1
  80. package/dist/dts/rpc.d.ts +31 -0
  81. package/dist/dts/rpc.d.ts.map +1 -1
  82. package/dist/esm/Attestor.js +8 -1
  83. package/dist/esm/Attestor.js.map +1 -1
  84. package/dist/esm/ChainRegistry.js +16 -0
  85. package/dist/esm/ChainRegistry.js.map +1 -0
  86. package/dist/esm/Domain.js +7 -1
  87. package/dist/esm/Domain.js.map +1 -1
  88. package/dist/esm/Error.js +1 -1
  89. package/dist/esm/Error.js.map +1 -1
  90. package/dist/esm/EvmPublicClient.js +4 -275
  91. package/dist/esm/EvmPublicClient.js.map +1 -1
  92. package/dist/esm/EvmWalletClient.js +5 -638
  93. package/dist/esm/EvmWalletClient.js.map +1 -1
  94. package/dist/esm/Payment.js +84 -78
  95. package/dist/esm/Payment.js.map +1 -1
  96. package/dist/esm/Prover.js +1 -1
  97. package/dist/esm/Prover.js.map +1 -1
  98. package/dist/esm/PublicClient.js +2 -2
  99. package/dist/esm/PublicClient.js.map +1 -1
  100. package/dist/esm/WalletClient.js +2 -2
  101. package/dist/esm/WalletClient.js.map +1 -1
  102. package/dist/esm/constants/ibc-core-registry.js +5 -6
  103. package/dist/esm/constants/ibc-core-registry.js.map +1 -1
  104. package/dist/esm/constants/z-asset-registry.js +7 -4
  105. package/dist/esm/constants/z-asset-registry.js.map +1 -1
  106. package/dist/esm/internal/evmPublicClient.js +253 -0
  107. package/dist/esm/internal/evmPublicClient.js.map +1 -0
  108. package/dist/esm/internal/evmWalletClient.js +647 -1
  109. package/dist/esm/internal/evmWalletClient.js.map +1 -1
  110. package/dist/esm/internal/publicClient.js +0 -2
  111. package/dist/esm/internal/publicClient.js.map +1 -1
  112. package/dist/esm/promises/Payment.js +2 -2
  113. package/dist/esm/promises/Payment.js.map +1 -1
  114. package/dist/esm/promises/PublicClient.js +2 -3
  115. package/dist/esm/promises/PublicClient.js.map +1 -1
  116. package/dist/esm/promises/WalletClient.js +0 -1
  117. package/dist/esm/promises/WalletClient.js.map +1 -1
  118. package/dist/esm/rpc.js +2 -4
  119. package/dist/esm/rpc.js.map +1 -1
  120. package/package.json +15 -3
  121. package/src/Attestor.ts +12 -3
  122. package/src/ChainRegistry.ts +27 -0
  123. package/src/Domain.ts +30 -9
  124. package/src/Error.ts +1 -0
  125. package/src/EvmPublicClient.ts +9 -488
  126. package/src/EvmWalletClient.ts +21 -973
  127. package/src/Payment.ts +234 -159
  128. package/src/Prover.ts +2 -2
  129. package/src/PublicClient.ts +11 -18
  130. package/src/WalletClient.ts +11 -7
  131. package/src/constants/ibc-core-registry.ts +14 -14
  132. package/src/constants/z-asset-registry.ts +10 -7
  133. package/src/internal/evmPublicClient.ts +459 -0
  134. package/src/internal/evmWalletClient.ts +1008 -0
  135. package/src/internal/publicClient.ts +0 -3
  136. package/src/promises/Payment.ts +26 -25
  137. package/src/promises/PublicClient.ts +2 -3
  138. package/src/promises/WalletClient.ts +0 -1
  139. package/src/rpc.ts +2 -4
  140. package/unionlabs-payments-0.3.1.tgz +0 -0
  141. package/src/tsdoc-metadata.json +0 -11
  142. /package/{constants → Constants}/ibc-core-registry/package.json +0 -0
  143. /package/{constants → Constants}/services/package.json +0 -0
  144. /package/{constants → Constants}/z-asset-registry/package.json +0 -0
@@ -66,8 +66,8 @@ export interface WalletClient {
66
66
  ) => Effect.Effect<Domain.PreparedRequest, SdkError, never>;
67
67
 
68
68
  readonly sign: (
69
- request: Domain.PreparedRequest,
70
- ) => Effect.Effect<Domain.SignedRequest, SdkError, never>;
69
+ request: ReadonlyArray<Domain.PreparedRequest> | Domain.PreparedRequest,
70
+ ) => Effect.Effect<Domain.SignedRequest[], SdkError, never>;
71
71
 
72
72
  // TODO: can be done by public client (?)
73
73
  readonly submit: (
@@ -94,7 +94,7 @@ export class SourceWalletClient extends Context.Tag("SourceWalletClient")<
94
94
  SourceWalletClient,
95
95
  WalletClient
96
96
  >() {
97
- static layer = <E, R>(layer: Layer.Layer<WalletClient, E, R>) =>
97
+ static project = <E, R>(layer: Layer.Layer<WalletClient, E, R>) =>
98
98
  Layer.project(layer, WalletClient, SourceWalletClient, identity);
99
99
  }
100
100
 
@@ -105,7 +105,7 @@ export class SourceWalletClient extends Context.Tag("SourceWalletClient")<
105
105
  export class DestinationWalletClient extends Context.Tag(
106
106
  "DestinationWalletClient",
107
107
  )<DestinationWalletClient, WalletClient>() {
108
- static layer = <E, R>(layer: Layer.Layer<WalletClient, E, R>) =>
108
+ static project = <E, R>(layer: Layer.Layer<WalletClient, E, R>) =>
109
109
  Layer.project(layer, WalletClient, DestinationWalletClient, identity);
110
110
  }
111
111
 
@@ -131,37 +131,41 @@ export namespace WalletClient {
131
131
  }
132
132
 
133
133
  export interface ApproveZAssetToSpendErc20 {
134
+ universalChainId: Domain.UniversalChainId;
134
135
  srcErc20Address: Domain.Erc20Address;
135
136
  amount: bigint;
136
137
  }
137
138
 
138
139
  export interface UpdateLoopbackClient {
140
+ universalChainId: Domain.UniversalChainId;
139
141
  clientId: number;
140
142
  height: bigint;
141
- ibcHandlerAddress: Domain.IbcHandlerAddress;
143
+ ibcHandlerAddress: Domain.IbcCoreAddress;
142
144
  }
143
145
 
144
146
  export interface DepositUnderlyingZAsset {
147
+ universalChainId: Domain.UniversalChainId;
145
148
  srcErc20Address: Domain.Erc20Address;
146
149
  amount: bigint;
147
150
  }
148
151
 
149
152
  export interface TransferZAsset {
153
+ universalChainId: Domain.UniversalChainId;
150
154
  srcErc20Address: Domain.Erc20Address;
151
155
  depositAddress: `0x${string}`;
152
156
  amount: bigint;
153
157
  }
154
158
 
155
159
  export type Deposit = {
160
+ destinationChainId: Domain.UniversalChainId;
156
161
  srcErc20Address: Domain.Erc20Address;
157
- sourceChainId: bigint;
158
162
  readonly beneficiaries: ReadonlyArray<Address>;
159
163
  depositAddress: Domain.ZAssetAddress;
160
- destinationChainId: bigint;
161
164
  amount: bigint;
162
165
  };
163
166
 
164
167
  export type Redeem = {
168
+ universalChainId: Domain.UniversalChainId;
165
169
  dstErc20Address: Domain.Erc20Address;
166
170
  unwrap?: boolean | undefined;
167
171
  } & RedeemParams;
@@ -1,25 +1,25 @@
1
- import { type Brand, Effect, flow, Option } from "effect";
1
+ import { Effect, flow, Option } from "effect";
2
2
  import * as Domain from "../Domain.js";
3
3
  import * as Error from "../Error.js";
4
4
 
5
- export type ZAssetAddress = `0x${string}` & Brand.Brand<"ZAssetAddress">;
6
-
7
5
  export const IBC_CORE_REGISTRY: {
8
- [chainId: string]: Domain.IbcHandlerAddress;
6
+ [chainId: Domain.UniversalChainId]: Domain.IbcCoreAddress;
9
7
  } = {
10
- "8453": Domain.IbcHandlerAddress(
8
+ [Domain.UniversalChainId.make("base.8453")]: Domain.IbcCoreAddress(
9
+ "0xee4ea8d358473f0fcebf0329feed95d56e8c04d7",
10
+ ),
11
+ [Domain.UniversalChainId.make("ethereum.1")]: Domain.IbcCoreAddress(
11
12
  "0xee4ea8d358473f0fcebf0329feed95d56e8c04d7",
12
13
  ),
13
14
  } as const;
14
15
 
15
- export function getIbcHandler(
16
- chainId: bigint,
17
- ): Option.Option<Domain.IbcHandlerAddress> {
18
- return Option.fromNullable(IBC_CORE_REGISTRY[`${chainId}`]);
19
- }
16
+ export const getIbcCoreAddress = (
17
+ ucid: Domain.UniversalChainId,
18
+ ): Option.Option<Domain.IbcCoreAddress> =>
19
+ Option.fromNullable(IBC_CORE_REGISTRY[ucid]);
20
20
 
21
- export const getIbcHandlerOrError = flow(
22
- getIbcHandler,
21
+ export const getIbcCoreAddressOrError = flow(
22
+ getIbcCoreAddress,
23
23
  Effect.mapError(
24
24
  (cause) =>
25
25
  new Error.SystemError({
@@ -31,8 +31,8 @@ export const getIbcHandlerOrError = flow(
31
31
  ),
32
32
  );
33
33
 
34
- export const getIbcHandlerOrThrow = flow(
35
- getIbcHandler,
34
+ export const getIbcCoreAddressOrThrow = flow(
35
+ getIbcCoreAddress,
36
36
  Option.getOrThrowWith(
37
37
  () =>
38
38
  new Error.SystemError({
@@ -4,22 +4,25 @@ import * as Domain from "../Domain.js";
4
4
  import * as Error from "../Error.js";
5
5
 
6
6
  export const Z_ASSET_REGISTRY: {
7
- [chainId: string]: {
7
+ [chainId: Domain.UniversalChainId]: {
8
8
  [assetAddress: Erc20Address]: Domain.ZAssetAddress;
9
9
  };
10
10
  } = {
11
- "8453": {
11
+ [Domain.UniversalChainId.make("base.8453")]: {
12
12
  [Domain.Erc20Address("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")]:
13
13
  Domain.ZAssetAddress("0xF0000101561619d8A61ABd045F47Af4f41Afe62D"),
14
14
  },
15
+ [Domain.UniversalChainId.make("ethereum.1")]: {
16
+ [Domain.Erc20Address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")]: // USDC on ethereum.1
17
+ Domain.ZAssetAddress("0xF0000101561619d8A61ABd045F47Af4f41Afe62D"),
18
+ },
15
19
  } as const;
16
20
 
17
- export function getZAsset(
18
- chainId: bigint,
21
+ export const getZAsset = (
22
+ universalChainId: Domain.UniversalChainId,
19
23
  assetAddress: Erc20Address,
20
- ): Option.Option<Domain.ZAssetAddress> {
21
- return Option.fromNullable(Z_ASSET_REGISTRY[`${chainId}`]?.[assetAddress]);
22
- }
24
+ ): Option.Option<Domain.ZAssetAddress> =>
25
+ Option.fromNullable(Z_ASSET_REGISTRY?.[universalChainId]?.[assetAddress]);
23
26
 
24
27
  export const getZAssetOrError = flow(
25
28
  getZAsset,
@@ -0,0 +1,459 @@
1
+ import * as Duration from "effect/Duration";
2
+ import * as Effect from "effect/Effect";
3
+ import { pipe } from "effect/Function";
4
+ import * as S from "effect/Schema";
5
+ import type * as Viem from "viem";
6
+ import { bytesToHex, hexToBytes } from "viem";
7
+ import * as Abi from "../Abi.js";
8
+ import * as ZAssetRegistry from "../constants/z-asset-registry.js";
9
+ import * as Domain from "../Domain.js";
10
+ import * as Error from "../Error.js";
11
+ import type * as EvmPublicClient from "../EvmPublicClient.js";
12
+ import * as Client from "../PublicClient.js";
13
+ import * as PS from "../Schema.js";
14
+
15
+ /** @internal */
16
+ export const TypeId: EvmPublicClient.TypeId = Symbol.for(
17
+ "@unionlabs/payments/EvmPublicClient",
18
+ ) as EvmPublicClient.TypeId;
19
+
20
+ export const makeViem = (
21
+ client: Viem.PublicClient,
22
+ ): Effect.Effect<EvmPublicClient.EvmPublicClient, Error.SdkError, never> =>
23
+ Effect.gen(function* () {
24
+ // const Viem = yield* Effect.tryPromise({
25
+ // try: () => import("viem").then((x) => x.default),
26
+ // catch: (cause) =>
27
+ // new Error.SystemError({
28
+ // reason: "DynamicImport",
29
+ // method: "makeViem",
30
+ // module: "EvmPublicClient",
31
+ // cause,
32
+ // description: "could not import viem",
33
+ // }),
34
+ // });
35
+
36
+ const readContract = Effect.fn("readContract")(
37
+ <
38
+ TAbi extends Viem.Abi,
39
+ A,
40
+ I,
41
+ R = never,
42
+ TFunctionName extends Viem.ContractFunctionName<
43
+ TAbi,
44
+ "pure" | "view"
45
+ > = Viem.ContractFunctionName<TAbi, "pure" | "view">,
46
+ TArgs extends Viem.ContractFunctionArgs<
47
+ TAbi,
48
+ "pure" | "view",
49
+ TFunctionName
50
+ > = Viem.ContractFunctionArgs<TAbi, "pure" | "view", TFunctionName>,
51
+ >(
52
+ params: Viem.ReadContractParameters<TAbi, TFunctionName, TArgs>,
53
+ schema: S.Schema<A, I, R>,
54
+ ): Effect.Effect<A, Error.SdkError, R> =>
55
+ pipe(
56
+ Effect.tryPromise({
57
+ try: () => client.readContract(params),
58
+ catch: (cause) =>
59
+ new Error.SystemError({
60
+ method: "readContract",
61
+ module: "EvmPublicClient",
62
+ reason: "ReadContract",
63
+ cause,
64
+ }),
65
+ }),
66
+ Effect.flatMap(S.decodeUnknown(schema)),
67
+ Effect.catchTag(
68
+ "ParseError",
69
+ (cause) =>
70
+ new Error.SystemError({
71
+ method: "readContract",
72
+ module: "EvmPublicClient",
73
+ reason: "InvalidData",
74
+ cause,
75
+ }),
76
+ ),
77
+ ),
78
+ );
79
+
80
+ const getLatestHeight = Effect.fn("getLatestHeight")(
81
+ (lightClientAddress: Domain.LightClientAddress, clientId: number) =>
82
+ readContract(
83
+ {
84
+ address: lightClientAddress,
85
+ abi: Abi.LIGHT_CLIENT_ABI,
86
+ functionName: "getLatestHeight",
87
+ args: [clientId],
88
+ },
89
+ S.BigIntFromSelf,
90
+ ),
91
+ );
92
+
93
+ const getLightClientAddress = Effect.fn("getLightClientAddress")(function* (
94
+ ibcStoreAddress: Domain.IbcCoreAddress,
95
+ clientId: number,
96
+ ) {
97
+ const res = yield* readContract(
98
+ {
99
+ address: ibcStoreAddress,
100
+ abi: Abi.IBC_STORE_ABI,
101
+ functionName: "getClient",
102
+ args: [clientId],
103
+ },
104
+ PS.Hex,
105
+ );
106
+
107
+ return Domain.LightClientAddress(res);
108
+ });
109
+
110
+ const getStateRootIndex = Effect.fn("getStateRootIndex")(
111
+ (zAssetAddress: Domain.ZAssetAddress, clientId: number) =>
112
+ readContract(
113
+ {
114
+ address: zAssetAddress,
115
+ abi: Abi.ZASSET_ABI,
116
+ functionName: "stateRootIndex",
117
+ args: [clientId],
118
+ },
119
+ S.BigIntFromSelf,
120
+ ),
121
+ );
122
+
123
+ const getConsensusState = Effect.fn("getConsensusState")(
124
+ (
125
+ lightClientAddress: Domain.LightClientAddress,
126
+ clientId: number,
127
+ height: bigint,
128
+ stateRootIndex: bigint,
129
+ ) =>
130
+ pipe(
131
+ readContract(
132
+ {
133
+ address: lightClientAddress,
134
+ abi: Abi.LIGHT_CLIENT_ABI,
135
+ functionName: "getConsensusState",
136
+ args: [clientId, height],
137
+ },
138
+ PS.Hex,
139
+ ),
140
+ // XXX: this can definitely fail
141
+ Effect.map((consensusBytes) => {
142
+ const bytes: Uint8Array = hexToBytes(consensusBytes);
143
+ const idx = Number(stateRootIndex);
144
+ const stateRoot = bytesToHex(bytes.slice(idx, idx + 32));
145
+ return { stateRoot } as const;
146
+ }),
147
+ ),
148
+ );
149
+
150
+ const getIbcHandlerAddress = Effect.fn("getIbcHandlerAddress")(function* (
151
+ zAssetAddress: Domain.ZAssetAddress,
152
+ ) {
153
+ return Domain.IbcCoreAddress(
154
+ yield* readContract(
155
+ {
156
+ address: zAssetAddress,
157
+ abi: Abi.ZASSET_ABI,
158
+ functionName: "ibcHandler",
159
+ },
160
+ PS.Hex,
161
+ ),
162
+ );
163
+ });
164
+
165
+ const readZAssetTokenBalance = Effect.fn("readZAssetTokenBalance")(
166
+ (
167
+ tokenAddress: Domain.ZAssetAddress,
168
+ accountAddress: `0x${string}`,
169
+ blockNumber?: bigint,
170
+ ) =>
171
+ readContract(
172
+ {
173
+ address: tokenAddress,
174
+ abi: [
175
+ {
176
+ inputs: [{ name: "account", type: "address" }],
177
+ name: "balanceOf",
178
+ outputs: [{ name: "", type: "uint256" }],
179
+ stateMutability: "view",
180
+ type: "function",
181
+ },
182
+ ] as const,
183
+ functionName: "balanceOf",
184
+ args: [accountAddress],
185
+ blockNumber,
186
+ },
187
+ S.BigIntFromSelf,
188
+ ),
189
+ );
190
+
191
+ const readNullifierBalance = Effect.fn("readNullifierBalance")(
192
+ (zAssetAddress: Domain.ZAssetAddress, nullifier: bigint) =>
193
+ readContract(
194
+ {
195
+ address: zAssetAddress,
196
+ abi: Abi.ZASSET_ABI,
197
+ functionName: "nullifierBalance",
198
+ args: [nullifier],
199
+ },
200
+ S.BigIntFromSelf,
201
+ ),
202
+ );
203
+
204
+ const getLatestBlockNumber = Effect.tryPromise({
205
+ try: () => client.getBlockNumber(),
206
+ catch: (cause) =>
207
+ new Error.SystemError({
208
+ method: "getLatestBlockNumber",
209
+ module: "EvmPublicClient",
210
+ reason: "InvalidData",
211
+ cause,
212
+ }),
213
+ });
214
+
215
+ const readBalanceAtHeight = Effect.fn("readBalanceAtHeight")(
216
+ (
217
+ opts: Client.PublicClient.ReadBalanceAtHeightOptions,
218
+ ): Effect.Effect<
219
+ Client.PublicClient.BalanceAtHeightInfo,
220
+ Error.SdkError
221
+ > =>
222
+ Effect.gen(function* () {
223
+ const latestHeight = yield* getLatestBlockNumber;
224
+
225
+ const srcZAssetAddress = yield* ZAssetRegistry.getZAsset(
226
+ opts.srcChainId,
227
+ opts.srcErc20Address,
228
+ ).pipe(
229
+ Effect.mapError(
230
+ (cause) =>
231
+ new Error.SystemError({
232
+ method: "readBalanceAtHeight",
233
+ module: "EvmPublicClient",
234
+ reason: "InvalidData",
235
+ cause,
236
+ }),
237
+ ),
238
+ );
239
+
240
+ const dstZAssetAddress = yield* ZAssetRegistry.getZAsset(
241
+ opts.dstChainId,
242
+ opts.dstErc20Address,
243
+ ).pipe(
244
+ Effect.mapError(
245
+ (cause) =>
246
+ new Error.SystemError({
247
+ method: "readBalanceAtHeight",
248
+ module: "EvmPublicClient",
249
+ reason: "InvalidData",
250
+ cause,
251
+ }),
252
+ ),
253
+ );
254
+
255
+ const [balanceAtTip, confirmed, redeemed] = yield* Effect.all(
256
+ [
257
+ // ZAsset balance of the deposit address ZAsset on the source chain at the tip of the chain
258
+ readZAssetTokenBalance(
259
+ srcZAssetAddress,
260
+ opts.depositAddress,
261
+ latestHeight,
262
+ ),
263
+ // ZAsset balance of the deposit address on the source chain at the (latest) light client height
264
+ readZAssetTokenBalance(
265
+ srcZAssetAddress,
266
+ opts.depositAddress,
267
+ opts.height,
268
+ ),
269
+ // amount that has already been redeemed under the specified nullifier
270
+ readNullifierBalance(dstZAssetAddress, opts.nullifier),
271
+ ],
272
+ { concurrency: "unbounded" },
273
+ );
274
+
275
+ const available = confirmed > redeemed ? confirmed - redeemed : 0n;
276
+ const pending =
277
+ balanceAtTip > confirmed ? balanceAtTip - confirmed : 0n;
278
+
279
+ return { confirmed, redeemed, available, pending, latestHeight };
280
+ }),
281
+ );
282
+
283
+ const readBalance = Effect.fn("readBalance")(
284
+ (
285
+ opts: Client.PublicClient.ReadBalanceOptions,
286
+ ): Effect.Effect<Client.PublicClient.BalanceInfo, Error.SdkError> =>
287
+ Effect.gen(function* () {
288
+ const dstZAssetAddress = yield* ZAssetRegistry.getZAsset(
289
+ opts.dstChainId,
290
+ opts.dstErc20Address,
291
+ ).pipe(
292
+ Effect.mapError(
293
+ (cause) =>
294
+ new Error.SystemError({
295
+ method: "readBalance",
296
+ module: "EvmPublicClient",
297
+ reason: "InvalidData",
298
+ cause,
299
+ }),
300
+ ),
301
+ );
302
+
303
+ yield* Effect.log({ opts, dstZAssetAddress });
304
+
305
+ const ibcHandlerAddress =
306
+ yield* getIbcHandlerAddress(dstZAssetAddress);
307
+
308
+ const lightClientAddress = yield* getLightClientAddress(
309
+ ibcHandlerAddress,
310
+ opts.clientId,
311
+ );
312
+ const lightClientHeight = yield* getLatestHeight(
313
+ lightClientAddress,
314
+ opts.clientId,
315
+ );
316
+
317
+ const balance = yield* readBalanceAtHeight({
318
+ srcErc20Address: opts.srcErc20Address,
319
+ srcChainId: opts.srcChainId,
320
+ dstErc20Address: opts.dstErc20Address,
321
+ dstChainId: opts.dstChainId,
322
+ depositAddress: opts.depositAddress,
323
+ nullifier: opts.nullifier,
324
+ height: lightClientHeight,
325
+ });
326
+
327
+ return { ...balance, lightClientHeight };
328
+ }),
329
+ );
330
+
331
+ const getCounterparty = Effect.fn("getCounterparty")(
332
+ (zAssetAddress: Domain.ZAssetAddress, clientId: number) =>
333
+ readContract(
334
+ {
335
+ address: zAssetAddress,
336
+ abi: Abi.ZASSET_ABI,
337
+ functionName: "counterparty",
338
+ args: [clientId],
339
+ },
340
+ Domain.CounterpartyInfo,
341
+ ),
342
+ );
343
+
344
+ const getProof = Effect.fn("getProof")(
345
+ (options: {
346
+ address: `0x${string}`;
347
+ storageKeys: `0x${string}`[];
348
+ blockNumber: bigint;
349
+ }) =>
350
+ Effect.tryPromise({
351
+ try: () => client.getProof(options),
352
+ catch: (cause) =>
353
+ new Error.SystemError({
354
+ method: "getProof",
355
+ module: "EvmPublicClient",
356
+ reason: "InvalidData",
357
+ cause,
358
+ }),
359
+ }),
360
+ );
361
+
362
+ const waitForTransactionReceipt = Effect.fn("waitForTransactionReceipt")(
363
+ (hash: Domain.TxHash) =>
364
+ pipe(
365
+ Effect.tryPromise({
366
+ try: () => client.waitForTransactionReceipt({ hash }),
367
+ catch: (cause) =>
368
+ new Error.SystemError({
369
+ method: "waitForTransactionReceipt",
370
+ module: "EvmPublicClient",
371
+ reason: "InvalidData",
372
+ cause,
373
+ }),
374
+ }),
375
+ Effect.flatMap((x) =>
376
+ x.status === "success"
377
+ ? Effect.succeed(x)
378
+ : Effect.fail(
379
+ new Error.SystemError({
380
+ method: "waitForTransactionReceipt",
381
+ module: "EvmPublicClient",
382
+ reason: "InvalidData",
383
+ description: "contract reverted",
384
+ }),
385
+ ),
386
+ ),
387
+ ),
388
+ );
389
+
390
+ const waitForBlock = Effect.fn("waitForBlock")(
391
+ (targetHeight: bigint): Effect.Effect<bigint, Error.SdkError> =>
392
+ pipe(
393
+ getLatestBlockNumber,
394
+ Effect.flatMap((initial) =>
395
+ Effect.iterate(initial, {
396
+ // one block extra to avoid loadbalancer issues
397
+ while: (height) => height <= targetHeight,
398
+ body: () =>
399
+ pipe(
400
+ Effect.sleep(Duration.seconds(1)),
401
+ Effect.andThen(getLatestBlockNumber),
402
+ ),
403
+ }),
404
+ ),
405
+ ),
406
+ );
407
+
408
+ const simulateCalls = Effect.fn("simulateCalls")(
409
+ (params: {
410
+ calls: Array<{
411
+ to: `0x${string}`;
412
+ data: `0x${string}`;
413
+ value?: bigint | undefined;
414
+ }>;
415
+ account?: `0x${string}` | undefined;
416
+ }) =>
417
+ Effect.tryPromise({
418
+ try: async () => {
419
+ const { simulateCalls } = await import("viem/actions");
420
+ return simulateCalls(client, {
421
+ calls: params.calls.map((call) => ({
422
+ to: call.to,
423
+ data: call.data,
424
+ value: call.value,
425
+ })),
426
+ account: params.account,
427
+ });
428
+ },
429
+ catch: (cause) =>
430
+ new Error.SystemError({
431
+ method: "simulateCalls",
432
+ module: "EvmPublicClient",
433
+ reason: "InvalidData",
434
+ cause,
435
+ }),
436
+ }),
437
+ );
438
+
439
+ return Object.assign(
440
+ yield* Client.make({
441
+ getLatestHeight,
442
+ getLatestBlockNumber,
443
+ readBalance,
444
+ readBalanceAtHeight,
445
+ getLightClientAddress,
446
+ getStateRootIndex,
447
+ getConsensusState,
448
+ getIbcHandlerAddress,
449
+ getCounterparty,
450
+ getProof,
451
+ waitForBlock,
452
+ }),
453
+ {
454
+ [TypeId]: TypeId as EvmPublicClient.TypeId,
455
+ waitForTransactionReceipt,
456
+ simulateCalls,
457
+ },
458
+ );
459
+ });