@unionlabs/payments 0.1.0 → 0.2.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/Abi/package.json +6 -0
- package/Attestor/package.json +6 -0
- package/Constants/package.json +6 -0
- package/Domain/package.json +6 -0
- package/Error/package.json +6 -0
- package/EvmPublicClient/package.json +6 -0
- package/EvmWalletClient/package.json +6 -0
- package/Payment/package.json +6 -0
- package/PaymentError/package.json +6 -0
- package/Prover/package.json +6 -0
- package/PublicClient/package.json +6 -0
- package/README.md +1 -0
- package/Schema/package.json +6 -0
- package/Utils/package.json +6 -0
- package/WalletClient/package.json +6 -0
- package/attestation/package.json +6 -0
- package/cli/commands/balance/package.json +6 -0
- package/cli/commands/deposit/package.json +6 -0
- package/cli/commands/export-verifier/package.json +6 -0
- package/cli/commands/generate/package.json +6 -0
- package/cli/commands/history/package.json +6 -0
- package/cli/commands/prove/package.json +6 -0
- package/cli/commands/redeem/package.json +6 -0
- package/cli/commands/update-client/package.json +6 -0
- package/cli/config/package.json +6 -0
- package/cli/package.json +6 -0
- package/cli/utils/package.json +6 -0
- package/constants/ibc-core-registry/package.json +6 -0
- package/constants/services/package.json +6 -0
- package/constants/z-asset-registry/package.json +6 -0
- package/dist/cjs/Abi.js +270 -0
- package/dist/cjs/Abi.js.map +1 -0
- package/dist/cjs/Attestor.js +76 -0
- package/dist/cjs/Attestor.js.map +1 -0
- package/dist/cjs/Constants.js +8 -0
- package/dist/cjs/Constants.js.map +1 -0
- package/dist/cjs/Domain.js +24 -0
- package/dist/cjs/Domain.js.map +1 -0
- package/dist/cjs/Error.js +100 -0
- package/dist/cjs/Error.js.map +1 -0
- package/dist/cjs/EvmPublicClient.js +301 -0
- package/dist/cjs/EvmPublicClient.js.map +1 -0
- package/dist/cjs/EvmWalletClient.js +670 -0
- package/dist/cjs/EvmWalletClient.js.map +1 -0
- package/dist/cjs/Payment.js +333 -0
- package/dist/cjs/Payment.js.map +1 -0
- package/dist/cjs/PaymentError.js +32 -0
- package/dist/cjs/PaymentError.js.map +1 -0
- package/dist/cjs/Prover.js +153 -0
- package/dist/cjs/Prover.js.map +1 -0
- package/dist/cjs/PublicClient.js +39 -0
- package/dist/cjs/PublicClient.js.map +1 -0
- package/dist/cjs/Schema.js +38 -0
- package/dist/cjs/Schema.js.map +1 -0
- package/dist/cjs/Utils.js +33 -0
- package/dist/cjs/Utils.js.map +1 -0
- package/dist/cjs/WalletClient.js +39 -0
- package/dist/cjs/WalletClient.js.map +1 -0
- package/dist/cjs/attestation.js +49 -0
- package/dist/cjs/attestation.js.map +1 -0
- package/dist/cjs/cli/commands/balance.js +60 -0
- package/dist/cjs/cli/commands/balance.js.map +1 -0
- package/dist/cjs/cli/commands/deposit.js +58 -0
- package/dist/cjs/cli/commands/deposit.js.map +1 -0
- package/dist/cjs/cli/commands/export-verifier.js +27 -0
- package/dist/cjs/cli/commands/export-verifier.js.map +1 -0
- package/dist/cjs/cli/commands/generate.js +41 -0
- package/dist/cjs/cli/commands/generate.js.map +1 -0
- package/dist/cjs/cli/commands/history.js +59 -0
- package/dist/cjs/cli/commands/history.js.map +1 -0
- package/dist/cjs/cli/commands/prove.js +82 -0
- package/dist/cjs/cli/commands/prove.js.map +1 -0
- package/dist/cjs/cli/commands/redeem.js +152 -0
- package/dist/cjs/cli/commands/redeem.js.map +1 -0
- package/dist/cjs/cli/commands/update-client.js +62 -0
- package/dist/cjs/cli/commands/update-client.js.map +1 -0
- package/dist/cjs/cli/config.js +32 -0
- package/dist/cjs/cli/config.js.map +1 -0
- package/dist/cjs/cli/utils.js +108 -0
- package/dist/cjs/cli/utils.js.map +1 -0
- package/dist/cjs/cli.js +24 -0
- package/dist/cjs/cli.js.map +1 -0
- package/dist/cjs/constants/ibc-core-registry.js +30 -0
- package/dist/cjs/constants/ibc-core-registry.js.map +1 -0
- package/dist/cjs/constants/services.js +9 -0
- package/dist/cjs/constants/services.js.map +1 -0
- package/dist/cjs/constants/z-asset-registry.js +32 -0
- package/dist/cjs/constants/z-asset-registry.js.map +1 -0
- package/dist/cjs/gen/prover_pb.js +81 -0
- package/dist/cjs/gen/prover_pb.js.map +1 -0
- package/dist/cjs/index.js +32 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal/evm.js +191 -0
- package/dist/cjs/internal/evm.js.map +1 -0
- package/dist/cjs/internal/evmWalletClient.js +6 -0
- package/dist/cjs/internal/evmWalletClient.js.map +1 -0
- package/dist/cjs/internal/publicClient.js +49 -0
- package/dist/cjs/internal/publicClient.js.map +1 -0
- package/dist/cjs/internal/walletClient.js +43 -0
- package/dist/cjs/internal/walletClient.js.map +1 -0
- package/dist/cjs/legacy/client.js +1222 -0
- package/dist/cjs/legacy/client.js.map +1 -0
- package/dist/cjs/legacy/prover.js +112 -0
- package/dist/cjs/legacy/prover.js.map +1 -0
- package/dist/cjs/poseidon2.js +226 -0
- package/dist/cjs/poseidon2.js.map +1 -0
- package/dist/cjs/promises/Attestor.js +23 -0
- package/dist/cjs/promises/Attestor.js.map +1 -0
- package/dist/cjs/promises/EvmPublicClient.js +34 -0
- package/dist/cjs/promises/EvmPublicClient.js.map +1 -0
- package/dist/cjs/promises/EvmWalletClient.js +51 -0
- package/dist/cjs/promises/EvmWalletClient.js.map +1 -0
- package/dist/cjs/promises/Payment.js +22 -0
- package/dist/cjs/promises/Payment.js.map +1 -0
- package/dist/cjs/promises/Prover.js +26 -0
- package/dist/cjs/promises/Prover.js.map +1 -0
- package/dist/cjs/promises/PublicClient.js +53 -0
- package/dist/cjs/promises/PublicClient.js.map +1 -0
- package/dist/cjs/promises/WalletClient.js +44 -0
- package/dist/cjs/promises/WalletClient.js.map +1 -0
- package/dist/cjs/promises/index.js +22 -0
- package/dist/cjs/promises/index.js.map +1 -0
- package/dist/cjs/rpc.js +867 -0
- package/dist/cjs/rpc.js.map +1 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/dts/Abi.d.ts +220 -0
- package/dist/dts/Abi.d.ts.map +1 -0
- package/dist/dts/Attestor.d.ts +42 -0
- package/dist/dts/Attestor.d.ts.map +1 -0
- package/dist/dts/Constants.d.ts +3 -0
- package/dist/dts/Constants.d.ts.map +1 -0
- package/dist/dts/Domain.d.ts +141 -0
- package/dist/dts/Domain.d.ts.map +1 -0
- package/dist/dts/Error.d.ts +102 -0
- package/dist/dts/Error.d.ts.map +1 -0
- package/dist/dts/EvmPublicClient.d.ts +61 -0
- package/dist/dts/EvmPublicClient.d.ts.map +1 -0
- package/dist/dts/EvmWalletClient.d.ts +67 -0
- package/dist/dts/EvmWalletClient.d.ts.map +1 -0
- package/dist/dts/Payment.d.ts +128 -0
- package/dist/dts/Payment.d.ts.map +1 -0
- package/dist/dts/PaymentError.d.ts +21 -0
- package/dist/dts/PaymentError.d.ts.map +1 -0
- package/dist/dts/Prover.d.ts +87 -0
- package/dist/dts/Prover.d.ts.map +1 -0
- package/dist/dts/PublicClient.d.ts +146 -0
- package/dist/dts/PublicClient.d.ts.map +1 -0
- package/dist/dts/Schema.d.ts +16 -0
- package/dist/dts/Schema.d.ts.map +1 -0
- package/dist/dts/Utils.d.ts +11 -0
- package/dist/dts/Utils.d.ts.map +1 -0
- package/dist/dts/WalletClient.d.ts +123 -0
- package/dist/dts/WalletClient.d.ts.map +1 -0
- package/dist/dts/attestation.d.ts +13 -0
- package/dist/dts/attestation.d.ts.map +1 -0
- package/dist/dts/cli/commands/balance.d.ts +3 -0
- package/dist/dts/cli/commands/balance.d.ts.map +1 -0
- package/dist/dts/cli/commands/deposit.d.ts +3 -0
- package/dist/dts/cli/commands/deposit.d.ts.map +1 -0
- package/dist/dts/cli/commands/export-verifier.d.ts +3 -0
- package/dist/dts/cli/commands/export-verifier.d.ts.map +1 -0
- package/dist/dts/cli/commands/generate.d.ts +3 -0
- package/dist/dts/cli/commands/generate.d.ts.map +1 -0
- package/dist/dts/cli/commands/history.d.ts +3 -0
- package/dist/dts/cli/commands/history.d.ts.map +1 -0
- package/dist/dts/cli/commands/prove.d.ts +3 -0
- package/dist/dts/cli/commands/prove.d.ts.map +1 -0
- package/dist/dts/cli/commands/redeem.d.ts +3 -0
- package/dist/dts/cli/commands/redeem.d.ts.map +1 -0
- package/dist/dts/cli/commands/update-client.d.ts +3 -0
- package/dist/dts/cli/commands/update-client.d.ts.map +1 -0
- package/dist/dts/cli/config.d.ts +14 -0
- package/dist/dts/cli/config.d.ts.map +1 -0
- package/dist/dts/cli/utils.d.ts +11 -0
- package/dist/dts/cli/utils.d.ts.map +1 -0
- package/dist/dts/cli.d.ts +3 -0
- package/dist/dts/cli.d.ts.map +1 -0
- package/dist/dts/constants/ibc-core-registry.d.ts +11 -0
- package/dist/dts/constants/ibc-core-registry.d.ts.map +1 -0
- package/dist/dts/constants/services.d.ts +3 -0
- package/dist/dts/constants/services.d.ts.map +1 -0
- package/dist/dts/constants/z-asset-registry.d.ts +13 -0
- package/dist/dts/constants/z-asset-registry.d.ts.map +1 -0
- package/dist/dts/gen/prover_pb.d.ts +300 -0
- package/dist/dts/gen/prover_pb.d.ts.map +1 -0
- package/dist/dts/index.d.ts +21 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/internal/evm.d.ts +250 -0
- package/dist/dts/internal/evm.d.ts.map +1 -0
- package/dist/dts/internal/evmWalletClient.d.ts +2 -0
- package/dist/dts/internal/evmWalletClient.d.ts.map +1 -0
- package/dist/dts/internal/publicClient.d.ts +2 -0
- package/dist/dts/internal/publicClient.d.ts.map +1 -0
- package/dist/dts/internal/walletClient.d.ts +2 -0
- package/dist/dts/internal/walletClient.d.ts.map +1 -0
- package/dist/dts/legacy/client.d.ts +313 -0
- package/dist/dts/legacy/client.d.ts.map +1 -0
- package/dist/dts/legacy/prover.d.ts +30 -0
- package/dist/dts/legacy/prover.d.ts.map +1 -0
- package/dist/dts/poseidon2.d.ts +18 -0
- package/dist/dts/poseidon2.d.ts.map +1 -0
- package/dist/dts/promises/Attestor.d.ts +17 -0
- package/dist/dts/promises/Attestor.d.ts.map +1 -0
- package/dist/dts/promises/EvmPublicClient.d.ts +3709 -0
- package/dist/dts/promises/EvmPublicClient.d.ts.map +1 -0
- package/dist/dts/promises/EvmWalletClient.d.ts +4502 -0
- package/dist/dts/promises/EvmWalletClient.d.ts.map +1 -0
- package/dist/dts/promises/Payment.d.ts +33 -0
- package/dist/dts/promises/Payment.d.ts.map +1 -0
- package/dist/dts/promises/Prover.d.ts +14 -0
- package/dist/dts/promises/Prover.d.ts.map +1 -0
- package/dist/dts/promises/PublicClient.d.ts +23 -0
- package/dist/dts/promises/PublicClient.d.ts.map +1 -0
- package/dist/dts/promises/WalletClient.d.ts +57 -0
- package/dist/dts/promises/WalletClient.d.ts.map +1 -0
- package/dist/dts/promises/index.d.ts +8 -0
- package/dist/dts/promises/index.d.ts.map +1 -0
- package/dist/dts/rpc.d.ts +148 -0
- package/dist/dts/rpc.d.ts.map +1 -0
- package/dist/dts/types.d.ts +263 -0
- package/dist/dts/types.d.ts.map +1 -0
- package/dist/esm/Abi.js +264 -0
- package/dist/esm/Abi.js.map +1 -0
- package/dist/esm/Attestor.js +68 -0
- package/dist/esm/Attestor.js.map +1 -0
- package/dist/esm/Constants.js +2 -0
- package/dist/esm/Constants.js.map +1 -0
- package/dist/esm/Domain.js +17 -0
- package/dist/esm/Domain.js.map +1 -0
- package/dist/esm/Error.js +89 -0
- package/dist/esm/Error.js.map +1 -0
- package/dist/esm/EvmPublicClient.js +292 -0
- package/dist/esm/EvmPublicClient.js.map +1 -0
- package/dist/esm/EvmWalletClient.js +659 -0
- package/dist/esm/EvmWalletClient.js.map +1 -0
- package/dist/esm/Payment.js +323 -0
- package/dist/esm/Payment.js.map +1 -0
- package/dist/esm/PaymentError.js +24 -0
- package/dist/esm/PaymentError.js.map +1 -0
- package/dist/esm/Prover.js +142 -0
- package/dist/esm/Prover.js.map +1 -0
- package/dist/esm/PublicClient.js +30 -0
- package/dist/esm/PublicClient.js.map +1 -0
- package/dist/esm/Schema.js +31 -0
- package/dist/esm/Schema.js.map +1 -0
- package/dist/esm/Utils.js +27 -0
- package/dist/esm/Utils.js.map +1 -0
- package/dist/esm/WalletClient.js +30 -0
- package/dist/esm/WalletClient.js.map +1 -0
- package/dist/esm/attestation.js +42 -0
- package/dist/esm/attestation.js.map +1 -0
- package/dist/esm/cli/commands/balance.js +54 -0
- package/dist/esm/cli/commands/balance.js.map +1 -0
- package/dist/esm/cli/commands/deposit.js +52 -0
- package/dist/esm/cli/commands/deposit.js.map +1 -0
- package/dist/esm/cli/commands/export-verifier.js +21 -0
- package/dist/esm/cli/commands/export-verifier.js.map +1 -0
- package/dist/esm/cli/commands/generate.js +35 -0
- package/dist/esm/cli/commands/generate.js.map +1 -0
- package/dist/esm/cli/commands/history.js +53 -0
- package/dist/esm/cli/commands/history.js.map +1 -0
- package/dist/esm/cli/commands/prove.js +76 -0
- package/dist/esm/cli/commands/prove.js.map +1 -0
- package/dist/esm/cli/commands/redeem.js +146 -0
- package/dist/esm/cli/commands/redeem.js.map +1 -0
- package/dist/esm/cli/commands/update-client.js +56 -0
- package/dist/esm/cli/commands/update-client.js.map +1 -0
- package/dist/esm/cli/config.js +26 -0
- package/dist/esm/cli/config.js.map +1 -0
- package/dist/esm/cli/utils.js +94 -0
- package/dist/esm/cli/utils.js.map +1 -0
- package/dist/esm/cli.js +22 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/constants/ibc-core-registry.js +21 -0
- package/dist/esm/constants/ibc-core-registry.js.map +1 -0
- package/dist/esm/constants/services.js +3 -0
- package/dist/esm/constants/services.js.map +1 -0
- package/dist/esm/constants/z-asset-registry.js +23 -0
- package/dist/esm/constants/z-asset-registry.js.map +1 -0
- package/dist/esm/gen/prover_pb.js +74 -0
- package/dist/esm/gen/prover_pb.js.map +1 -0
- package/dist/esm/index.js +22 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/evm.js +169 -0
- package/dist/esm/internal/evm.js.map +1 -0
- package/dist/esm/internal/evmWalletClient.js +2 -0
- package/dist/esm/internal/evmWalletClient.js.map +1 -0
- package/dist/esm/internal/publicClient.js +41 -0
- package/dist/esm/internal/publicClient.js.map +1 -0
- package/dist/esm/internal/walletClient.js +35 -0
- package/dist/esm/internal/walletClient.js.map +1 -0
- package/dist/esm/legacy/client.js +1212 -0
- package/dist/esm/legacy/client.js.map +1 -0
- package/dist/esm/legacy/prover.js +105 -0
- package/dist/esm/legacy/prover.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/poseidon2.js +218 -0
- package/dist/esm/poseidon2.js.map +1 -0
- package/dist/esm/promises/Attestor.js +14 -0
- package/dist/esm/promises/Attestor.js.map +1 -0
- package/dist/esm/promises/EvmPublicClient.js +26 -0
- package/dist/esm/promises/EvmPublicClient.js.map +1 -0
- package/dist/esm/promises/EvmWalletClient.js +43 -0
- package/dist/esm/promises/EvmWalletClient.js.map +1 -0
- package/dist/esm/promises/Payment.js +13 -0
- package/dist/esm/promises/Payment.js.map +1 -0
- package/dist/esm/promises/Prover.js +17 -0
- package/dist/esm/promises/Prover.js.map +1 -0
- package/dist/esm/promises/PublicClient.js +45 -0
- package/dist/esm/promises/PublicClient.js.map +1 -0
- package/dist/esm/promises/WalletClient.js +36 -0
- package/dist/esm/promises/WalletClient.js.map +1 -0
- package/dist/esm/promises/index.js +8 -0
- package/dist/esm/promises/index.js.map +1 -0
- package/dist/esm/rpc.js +850 -0
- package/dist/esm/rpc.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/gen/prover_pb/package.json +6 -0
- package/index/package.json +6 -0
- package/legacy/client/package.json +6 -0
- package/legacy/prover/package.json +6 -0
- package/package.json +397 -44
- package/poseidon2/package.json +6 -0
- package/promises/Attestor/package.json +6 -0
- package/promises/EvmPublicClient/package.json +6 -0
- package/promises/EvmWalletClient/package.json +6 -0
- package/promises/Payment/package.json +6 -0
- package/promises/Prover/package.json +6 -0
- package/promises/PublicClient/package.json +6 -0
- package/promises/WalletClient/package.json +6 -0
- package/promises/index/package.json +6 -0
- package/promises/package.json +6 -0
- package/rpc/package.json +6 -0
- package/src/Abi.ts +195 -0
- package/src/Attestor.ts +113 -0
- package/src/Constants.ts +4 -0
- package/src/Domain.ts +52 -0
- package/src/Error.ts +163 -0
- package/src/EvmPublicClient.ts +549 -0
- package/src/EvmWalletClient.ts +1034 -0
- package/src/Payment.ts +523 -0
- package/src/PaymentError.ts +39 -0
- package/src/Prover.ts +240 -0
- package/src/PublicClient.ts +196 -0
- package/src/Schema.ts +36 -0
- package/src/Utils.ts +43 -0
- package/src/WalletClient.ts +172 -0
- package/src/attestation.ts +69 -0
- package/src/cli/commands/balance.ts +88 -0
- package/src/cli/commands/deposit.ts +104 -0
- package/src/cli/commands/export-verifier.ts +28 -0
- package/src/cli/commands/generate.ts +86 -0
- package/src/cli/commands/history.ts +91 -0
- package/src/cli/commands/prove.ts +133 -0
- package/src/cli/commands/redeem.ts +277 -0
- package/src/cli/commands/update-client.ts +96 -0
- package/src/cli/config.ts +55 -0
- package/src/cli/utils.ts +136 -0
- package/src/cli.ts +31 -0
- package/src/constants/ibc-core-registry.ts +44 -0
- package/src/constants/services.ts +4 -0
- package/src/constants/z-asset-registry.ts +47 -0
- package/src/gen/prover_pb.ts +375 -0
- package/src/index.ts +23 -0
- package/src/internal/evm.ts +361 -0
- package/src/internal/evmWalletClient.ts +0 -0
- package/src/internal/publicClient.ts +57 -0
- package/src/internal/walletClient.ts +50 -0
- package/src/legacy/client.ts +1652 -0
- package/src/legacy/prover.ts +135 -0
- package/src/poseidon2.ts +246 -0
- package/src/promises/Attestor.ts +25 -0
- package/src/promises/EvmPublicClient.ts +39 -0
- package/src/promises/EvmWalletClient.ts +63 -0
- package/src/promises/Payment.ts +86 -0
- package/src/promises/Prover.ts +26 -0
- package/src/promises/PublicClient.ts +47 -0
- package/src/promises/WalletClient.ts +38 -0
- package/src/promises/index.ts +7 -0
- package/src/rpc.ts +994 -0
- package/src/types.ts +281 -0
- package/types/package.json +6 -0
- package/dist/LICENSE +0 -1
- package/dist/chunk-37PNLRA6.js +0 -2418
- package/dist/cli.cjs +0 -3031
- package/dist/cli.js +0 -675
- package/dist/index.cjs +0 -2451
- package/dist/index.js +0 -1
- package/dist/package.json +0 -18
- package/dist/payments.d.ts +0 -835
- /package/{dist → src}/tsdoc-metadata.json +0 -0
|
@@ -0,0 +1,1212 @@
|
|
|
1
|
+
import { Duration, Effect, pipe, Schedule } from "effect";
|
|
2
|
+
import { createPublicClient, encodeAbiParameters, hexToBigInt, http, keccak256, toRlp } from "viem";
|
|
3
|
+
import { AttestationClient } from "../attestation.js";
|
|
4
|
+
import { Z_ASSET_REGISTRY } from "../constants/z-asset-registry.js";
|
|
5
|
+
import * as evm from "../internal/evm.js";
|
|
6
|
+
import { computeNullifier, computeUnspendableAddress } from "../poseidon2.js";
|
|
7
|
+
import { computeStorageSlot, deterministicShuffleClients, fetchLightClients, fetchMptProof, IBC_STORE_ABI, parseProofJson, proofJsonToRedeemParams, RpcClient } from "../rpc.js";
|
|
8
|
+
import { ProverClient } from "./prover.js";
|
|
9
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
10
|
+
const commonRetry = {
|
|
11
|
+
schedule: /*#__PURE__*/Schedule.linear(/*#__PURE__*/Duration.seconds(2)),
|
|
12
|
+
times: 6
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Construct a {@link UnionPrivatePayments} client.
|
|
16
|
+
*
|
|
17
|
+
* @throws Error if asset is unrecognized.
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export const make = options => {
|
|
21
|
+
const proverUrl = options.proverUrl?.toString() ?? "https://prover.payments.union.build";
|
|
22
|
+
const attestorUrl = options.attestationUrl?.toString() ?? "https://attestor.payments.union.build/functions/v1/attest";
|
|
23
|
+
const zAssetAddress =
|
|
24
|
+
// @ts-expect-error
|
|
25
|
+
Z_ASSET_REGISTRY[`${options.chainId}`][options.assetAddress];
|
|
26
|
+
if (!zAssetAddress) throw new Error("Invalid asset address");
|
|
27
|
+
if (!options.attestorApiKey) throw new Error("No attestor API key");
|
|
28
|
+
return new UnionPrivatePayments({
|
|
29
|
+
proverUrl,
|
|
30
|
+
sourceRpcUrl: options.rpcUrl.toString(),
|
|
31
|
+
destinationRpcUrl: options.rpcUrl.toString(),
|
|
32
|
+
srcZAssetAddress: zAssetAddress,
|
|
33
|
+
dstZAssetAddress: zAssetAddress,
|
|
34
|
+
sourceChainId: options.chainId,
|
|
35
|
+
destinationChainId: options.chainId,
|
|
36
|
+
attestorUrl: attestorUrl,
|
|
37
|
+
attestorApiKey: options.attestorApiKey
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Union Private Payments client
|
|
42
|
+
*
|
|
43
|
+
* This class provides a high-level interface for performing private transfers
|
|
44
|
+
* using the Union protocol. It handles:
|
|
45
|
+
*
|
|
46
|
+
* - Generating unspendable addresses for deposits
|
|
47
|
+
*
|
|
48
|
+
* - Querying balances (confirmed, pending, redeemed, available)
|
|
49
|
+
*
|
|
50
|
+
* - Building witness data for proof generation
|
|
51
|
+
*
|
|
52
|
+
* - Communicating with the prover server
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* import { UnionPrivatePayments } from '@unionlabs/payments';
|
|
57
|
+
*
|
|
58
|
+
* const client = new UnionPrivatePayments({
|
|
59
|
+
* proverUrl: 'http://localhost:8080',
|
|
60
|
+
* sourceRpcUrl: 'https://eth-mainnet.alchemyapi.io/v2/...',
|
|
61
|
+
* destinationRpcUrl: 'https://arbitrum-mainnet.alchemyapi.io/v2/...',
|
|
62
|
+
* srcZAssetAddress: '0x...', // ZAsset on source chain
|
|
63
|
+
* dstZAssetAddress: '0x...', // ZAsset on destination chain
|
|
64
|
+
* sourceChainId: 1n,
|
|
65
|
+
* destinationChainId: 42161n,
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // Generate deposit address
|
|
69
|
+
* const address = client.getDepositAddress(secret, beneficiaries);
|
|
70
|
+
*
|
|
71
|
+
* // Check balance
|
|
72
|
+
* const balance = await client.getBalance({ depositAddress, nullifier, clientId });
|
|
73
|
+
*
|
|
74
|
+
* // Generate proof for redemption
|
|
75
|
+
* const proof = await client.generateProof(secret, beneficiaries, beneficiary, amount, clientIds);
|
|
76
|
+
* ```
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
export class UnionPrivatePayments {
|
|
80
|
+
config;
|
|
81
|
+
srcClient;
|
|
82
|
+
dstClient;
|
|
83
|
+
proverClient;
|
|
84
|
+
constructor(config) {
|
|
85
|
+
this.config = config;
|
|
86
|
+
this.srcClient = new RpcClient(config.sourceRpcUrl);
|
|
87
|
+
this.dstClient = new RpcClient(config.destinationRpcUrl);
|
|
88
|
+
this.proverClient = new ProverClient(config.proverUrl);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the deposit address for a given secret and beneficiaries
|
|
92
|
+
*
|
|
93
|
+
* The returned address is an "unspendable" address derived from the secret.
|
|
94
|
+
* Tokens sent to this address can only be redeemed via ZK proof.
|
|
95
|
+
*
|
|
96
|
+
* @param secret - The 32-byte secret as a hex string
|
|
97
|
+
* @param beneficiaries - Array of 1-4 beneficiary addresses (remaining slots zero-padded)
|
|
98
|
+
* @returns The unspendable deposit address
|
|
99
|
+
*/
|
|
100
|
+
getDepositAddress(secret, beneficiaries) {
|
|
101
|
+
const paddedBeneficiaries = this.padBeneficiaries(beneficiaries);
|
|
102
|
+
return computeUnspendableAddress(secret, this.config.destinationChainId, paddedBeneficiaries);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get the nullifier for a given paymentKey
|
|
106
|
+
*
|
|
107
|
+
* The nullifier is used to prevent double-spending. Each (paymentKey, chainId) pair
|
|
108
|
+
* produces a unique nullifier that is recorded on-chain when funds are redeemed.
|
|
109
|
+
*
|
|
110
|
+
* @param paymentKey - The 32-byte paymentKey as a hex string
|
|
111
|
+
* @returns The nullifier as a bigint
|
|
112
|
+
*/
|
|
113
|
+
getNullifier(paymentKey) {
|
|
114
|
+
return computeNullifier(paymentKey, this.config.destinationChainId);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get balance information
|
|
118
|
+
*
|
|
119
|
+
* Returns:
|
|
120
|
+
* - confirmed: balance visible to light client (provable)
|
|
121
|
+
* - redeemed: amount already redeemed via nullifier
|
|
122
|
+
* - available: amount that can be redeemed now (confirmed - redeemed)
|
|
123
|
+
* - pending: deposits not yet visible to light client
|
|
124
|
+
*
|
|
125
|
+
* @param options - Options for getting balance
|
|
126
|
+
* @returns Balance information
|
|
127
|
+
*/
|
|
128
|
+
async getBalance(options) {
|
|
129
|
+
const {
|
|
130
|
+
depositAddress,
|
|
131
|
+
nullifier,
|
|
132
|
+
clientId
|
|
133
|
+
} = options;
|
|
134
|
+
console.log(options);
|
|
135
|
+
console.log({
|
|
136
|
+
dstZAssetAddress: this.config.dstZAssetAddress
|
|
137
|
+
});
|
|
138
|
+
const ibcHandlerAddress = await this.dstClient.getIbcHandlerAddress(this.config.dstZAssetAddress);
|
|
139
|
+
const lightClientAddress = await this.dstClient.getLightClientAddress(ibcHandlerAddress, clientId);
|
|
140
|
+
const lightClientHeight = await this.dstClient.getLatestHeight(lightClientAddress, clientId);
|
|
141
|
+
const balance = await this.getBalanceAtHeight(depositAddress, nullifier, lightClientHeight);
|
|
142
|
+
return {
|
|
143
|
+
...balance,
|
|
144
|
+
lightClientHeight
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get balance information at a specific block height
|
|
149
|
+
*
|
|
150
|
+
* @param depositAddress - The deposit address (unspendable address)
|
|
151
|
+
* @param nullifier - The nullifier for this secret
|
|
152
|
+
* @param height - The block height to query confirmed balance at
|
|
153
|
+
* @returns Balance information at the given height
|
|
154
|
+
*/
|
|
155
|
+
async getBalanceAtHeight(depositAddress, nullifier, height) {
|
|
156
|
+
const latestHeight = await this.srcClient.getLatestBlockNumber();
|
|
157
|
+
const [balanceAtTip, confirmed, redeemed] = await Promise.all([this.srcClient.getBalance(this.config.srcZAssetAddress, depositAddress), this.srcClient.getBalance(this.config.srcZAssetAddress, depositAddress, height), this.dstClient.getNullifierBalance(this.config.dstZAssetAddress, nullifier)]);
|
|
158
|
+
const available = confirmed > redeemed ? confirmed - redeemed : 0n;
|
|
159
|
+
const pending = balanceAtTip > confirmed ? balanceAtTip - confirmed : 0n;
|
|
160
|
+
return {
|
|
161
|
+
confirmed,
|
|
162
|
+
redeemed,
|
|
163
|
+
available,
|
|
164
|
+
pending,
|
|
165
|
+
latestHeight
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Generate a proof for redeeming funds
|
|
170
|
+
*
|
|
171
|
+
* This method:
|
|
172
|
+
* 1. Fetches light client data from the destination chain
|
|
173
|
+
* 2. Deterministically shuffles clients for privacy
|
|
174
|
+
* 3. Fetches MPT proof from the source chain
|
|
175
|
+
* 4. Builds the witness data
|
|
176
|
+
* 5. Sends the witness to the prover server
|
|
177
|
+
* 6. Polls until the proof is ready
|
|
178
|
+
*
|
|
179
|
+
* @param secret - The 32-byte secret as a hex string
|
|
180
|
+
* @param beneficiaries - Array of 0-4 beneficiary addresses (empty array = unbounded mode)
|
|
181
|
+
* @param beneficiary - The beneficiary address to redeem to
|
|
182
|
+
* @param amount - Amount to redeem
|
|
183
|
+
* @param clientIds - Light client IDs to include in the proof (for anonymity set)
|
|
184
|
+
* @param selectedClientId - The specific light client ID to use for the proof
|
|
185
|
+
* @returns GenerateProofResult containing proof result and client-side metadata
|
|
186
|
+
*/
|
|
187
|
+
async generateProof(secret, beneficiaries, beneficiary, amount, clientIds, selectedClientId) {
|
|
188
|
+
// Validate inputs
|
|
189
|
+
if (!clientIds.includes(selectedClientId)) {
|
|
190
|
+
return {
|
|
191
|
+
proof: {
|
|
192
|
+
success: false,
|
|
193
|
+
error: `selectedClientId ${selectedClientId} not in clientIds`
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (beneficiary === ZERO_ADDRESS) {
|
|
198
|
+
return {
|
|
199
|
+
proof: {
|
|
200
|
+
success: false,
|
|
201
|
+
error: "Beneficiary address cannot be zero"
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const paddedBeneficiaries = this.padBeneficiaries(beneficiaries);
|
|
206
|
+
const depositAddress = this.getDepositAddress(secret, beneficiaries);
|
|
207
|
+
const lightClients = await fetchLightClients(this.dstClient, this.config.dstZAssetAddress, clientIds);
|
|
208
|
+
if (lightClients.length === 0) {
|
|
209
|
+
return {
|
|
210
|
+
proof: {
|
|
211
|
+
success: false,
|
|
212
|
+
error: "No valid light clients found"
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// Deterministic shuffle: same secret always produces the same ordering for privacy
|
|
217
|
+
const shuffled = deterministicShuffleClients(lightClients, secret);
|
|
218
|
+
const selectedClientIndex = shuffled.findIndex(c => c.clientId === selectedClientId);
|
|
219
|
+
if (selectedClientIndex === -1) {
|
|
220
|
+
return {
|
|
221
|
+
proof: {
|
|
222
|
+
success: false,
|
|
223
|
+
error: `Client ${selectedClientId} not found after fetching`
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const selectedClient = shuffled[selectedClientIndex];
|
|
228
|
+
const {
|
|
229
|
+
tokenAddressKey,
|
|
230
|
+
balanceSlot
|
|
231
|
+
} = await this.dstClient.getCounterparty(this.config.dstZAssetAddress, selectedClientId);
|
|
232
|
+
// Check if counterparty is configured for this light client
|
|
233
|
+
const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
234
|
+
if (balanceSlot === ZERO_BYTES32 || tokenAddressKey === ZERO_BYTES32) {
|
|
235
|
+
return {
|
|
236
|
+
proof: {
|
|
237
|
+
success: false,
|
|
238
|
+
error: `Light client ${selectedClientId} is not configured as a counterparty on the destination ZAsset. ` + `Please call setCounterparty() on the ZAsset contract first.`
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const mappingSlot = hexToBigInt(balanceSlot);
|
|
243
|
+
const nullifier = this.getNullifier(secret);
|
|
244
|
+
// Check balance at the selected light client's height
|
|
245
|
+
const balance = await this.getBalanceAtHeight(depositAddress, nullifier, selectedClient.height);
|
|
246
|
+
if (amount > balance.available) {
|
|
247
|
+
return {
|
|
248
|
+
proof: {
|
|
249
|
+
success: false,
|
|
250
|
+
error: `Insufficient available balance. Requested: ${amount}, Available: ${balance.available} ` + `(Confirmed at height ${selectedClient.height}: ${balance.confirmed}, Already redeemed: ${balance.redeemed})`
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const storageSlot = computeStorageSlot(depositAddress, mappingSlot);
|
|
255
|
+
const mptProof = await fetchMptProof(this.srcClient, this.config.srcZAssetAddress, storageSlot, selectedClient.height);
|
|
256
|
+
const witness = {
|
|
257
|
+
secret,
|
|
258
|
+
dstChainId: this.config.destinationChainId,
|
|
259
|
+
beneficiaries: paddedBeneficiaries,
|
|
260
|
+
beneficiary,
|
|
261
|
+
redeemAmount: amount,
|
|
262
|
+
alreadyRedeemed: balance.redeemed,
|
|
263
|
+
lightClients: shuffled,
|
|
264
|
+
selectedClientIndex,
|
|
265
|
+
mptProof,
|
|
266
|
+
srcZAssetAddress: this.config.srcZAssetAddress,
|
|
267
|
+
mappingSlot: `0x${mappingSlot.toString(16)}`
|
|
268
|
+
};
|
|
269
|
+
const proofResult = await this.proverClient.generateProof(witness);
|
|
270
|
+
if (proofResult.success) {
|
|
271
|
+
return {
|
|
272
|
+
proof: proofResult,
|
|
273
|
+
metadata: {
|
|
274
|
+
depositAddress,
|
|
275
|
+
beneficiary,
|
|
276
|
+
value: amount,
|
|
277
|
+
lightClients: shuffled,
|
|
278
|
+
nullifier
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
proof: proofResult
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Export the verifier contract from the prover server
|
|
288
|
+
*
|
|
289
|
+
* The verifier contract is used to verify proofs on-chain.
|
|
290
|
+
* It is circuit-specific and does not change between proofs.
|
|
291
|
+
*
|
|
292
|
+
* @returns The Solidity verifier contract source code
|
|
293
|
+
*/
|
|
294
|
+
async exportVerifier() {
|
|
295
|
+
return this.proverClient.exportVerifier();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get source ZAsset token information
|
|
299
|
+
*
|
|
300
|
+
* @returns Token symbol and decimals
|
|
301
|
+
*/
|
|
302
|
+
async getSrcZAssetInfo() {
|
|
303
|
+
const [symbol, decimals] = await Promise.all([this.srcClient.getSymbol(this.config.srcZAssetAddress), this.srcClient.getDecimals(this.config.srcZAssetAddress)]);
|
|
304
|
+
return {
|
|
305
|
+
symbol,
|
|
306
|
+
decimals
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get destination ZAsset token information
|
|
311
|
+
*
|
|
312
|
+
* @returns Token symbol and decimals
|
|
313
|
+
*/
|
|
314
|
+
async getDstZAssetInfo() {
|
|
315
|
+
const [symbol, decimals] = await Promise.all([this.dstClient.getSymbol(this.config.dstZAssetAddress), this.dstClient.getDecimals(this.config.dstZAssetAddress)]);
|
|
316
|
+
return {
|
|
317
|
+
symbol,
|
|
318
|
+
decimals
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Deposit underlying tokens to ZAsset and transfer to deposit address
|
|
323
|
+
*
|
|
324
|
+
* Executes 3 transactions: approve → deposit → transfer.
|
|
325
|
+
* The wallet client must be connected to the source chain.
|
|
326
|
+
*
|
|
327
|
+
* @param secret - The 32-byte secret as a hex string
|
|
328
|
+
* @param beneficiaries - Array of 0-4 beneficiary addresses
|
|
329
|
+
* @param amount - Amount to deposit (in underlying token's smallest unit)
|
|
330
|
+
* @param walletClient - viem WalletClient with account and chain configured
|
|
331
|
+
* @returns Transaction hash, deposit address, underlying token, and chain ID
|
|
332
|
+
*/
|
|
333
|
+
async deposit(options) {
|
|
334
|
+
const {
|
|
335
|
+
paymentKey,
|
|
336
|
+
beneficiaries,
|
|
337
|
+
amount,
|
|
338
|
+
walletClient
|
|
339
|
+
} = options;
|
|
340
|
+
if (!walletClient.account) {
|
|
341
|
+
throw new Error("WalletClient must have an account");
|
|
342
|
+
}
|
|
343
|
+
if (!walletClient.chain) {
|
|
344
|
+
throw new Error("WalletClient must have a chain configured");
|
|
345
|
+
}
|
|
346
|
+
return Effect.gen(this, function* () {
|
|
347
|
+
const depositAddress = this.getDepositAddress(paymentKey, beneficiaries);
|
|
348
|
+
// Get underlying token address
|
|
349
|
+
const underlyingToken = yield* pipe(evm.readContract({
|
|
350
|
+
address: this.config.srcZAssetAddress,
|
|
351
|
+
abi: [{
|
|
352
|
+
inputs: [],
|
|
353
|
+
name: "underlying",
|
|
354
|
+
outputs: [{
|
|
355
|
+
name: "",
|
|
356
|
+
type: "address"
|
|
357
|
+
}],
|
|
358
|
+
stateMutability: "view",
|
|
359
|
+
type: "function"
|
|
360
|
+
}],
|
|
361
|
+
functionName: "underlying"
|
|
362
|
+
}), Effect.retry(commonRetry), Effect.provideService(evm.PublicClient, {
|
|
363
|
+
client: this.srcClient.getClient()
|
|
364
|
+
}));
|
|
365
|
+
if (underlyingToken === ZERO_ADDRESS) {
|
|
366
|
+
return yield* Effect.fail(Error("ZAsset is not a wrapped token (underlying is zero address)"));
|
|
367
|
+
}
|
|
368
|
+
const ERC20_ABI = [{
|
|
369
|
+
inputs: [{
|
|
370
|
+
name: "spender",
|
|
371
|
+
type: "address"
|
|
372
|
+
}, {
|
|
373
|
+
name: "amount",
|
|
374
|
+
type: "uint256"
|
|
375
|
+
}],
|
|
376
|
+
name: "approve",
|
|
377
|
+
outputs: [{
|
|
378
|
+
name: "",
|
|
379
|
+
type: "bool"
|
|
380
|
+
}],
|
|
381
|
+
stateMutability: "nonpayable",
|
|
382
|
+
type: "function"
|
|
383
|
+
}];
|
|
384
|
+
const ZASSET_ABI = [{
|
|
385
|
+
inputs: [{
|
|
386
|
+
name: "amount",
|
|
387
|
+
type: "uint256"
|
|
388
|
+
}],
|
|
389
|
+
name: "deposit",
|
|
390
|
+
outputs: [],
|
|
391
|
+
stateMutability: "nonpayable",
|
|
392
|
+
type: "function"
|
|
393
|
+
}, {
|
|
394
|
+
inputs: [{
|
|
395
|
+
name: "to",
|
|
396
|
+
type: "address"
|
|
397
|
+
}, {
|
|
398
|
+
name: "amount",
|
|
399
|
+
type: "uint256"
|
|
400
|
+
}],
|
|
401
|
+
name: "transfer",
|
|
402
|
+
outputs: [{
|
|
403
|
+
name: "",
|
|
404
|
+
type: "bool"
|
|
405
|
+
}],
|
|
406
|
+
stateMutability: "nonpayable",
|
|
407
|
+
type: "function"
|
|
408
|
+
}];
|
|
409
|
+
// 1. Approve ZAsset to spend underlying tokens
|
|
410
|
+
const approveHash = yield* pipe(evm.writeContract({
|
|
411
|
+
address: underlyingToken,
|
|
412
|
+
abi: ERC20_ABI,
|
|
413
|
+
functionName: "approve",
|
|
414
|
+
args: [this.config.srcZAssetAddress, amount],
|
|
415
|
+
chain: walletClient.chain,
|
|
416
|
+
account: walletClient.account
|
|
417
|
+
}), Effect.retry(commonRetry));
|
|
418
|
+
const approveReceipt = yield* pipe(evm.waitForTransactionReceipt(approveHash), Effect.retry(commonRetry));
|
|
419
|
+
if (approveReceipt.status === "reverted") {
|
|
420
|
+
return yield* Effect.fail(Error(`Approve transaction reverted: ${approveHash}`));
|
|
421
|
+
}
|
|
422
|
+
// 2. Deposit underlying to get ZAsset
|
|
423
|
+
const depositHash = yield* pipe(evm.writeContract({
|
|
424
|
+
address: this.config.srcZAssetAddress,
|
|
425
|
+
abi: ZASSET_ABI,
|
|
426
|
+
functionName: "deposit",
|
|
427
|
+
args: [amount],
|
|
428
|
+
chain: walletClient.chain,
|
|
429
|
+
account: walletClient.account
|
|
430
|
+
}), Effect.retry(commonRetry));
|
|
431
|
+
const depositReceipt = yield* evm.waitForTransactionReceipt(depositHash).pipe(Effect.retry(commonRetry));
|
|
432
|
+
if (depositReceipt.status === "reverted") {
|
|
433
|
+
throw new Error(`Deposit transaction reverted: ${depositHash}`);
|
|
434
|
+
}
|
|
435
|
+
// 3. Transfer ZAsset to deposit address
|
|
436
|
+
const transferHash = yield* pipe(evm.writeContract({
|
|
437
|
+
address: this.config.srcZAssetAddress,
|
|
438
|
+
abi: ZASSET_ABI,
|
|
439
|
+
functionName: "transfer",
|
|
440
|
+
args: [depositAddress, amount],
|
|
441
|
+
chain: walletClient.chain,
|
|
442
|
+
account: walletClient.account
|
|
443
|
+
}), Effect.retry(commonRetry));
|
|
444
|
+
const transferReceipt = yield* evm.waitForTransactionReceipt(transferHash);
|
|
445
|
+
if (transferReceipt.status === "reverted") {
|
|
446
|
+
throw new Error(`Transfer transaction reverted: ${transferHash}`);
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
txHash: transferHash,
|
|
450
|
+
depositAddress,
|
|
451
|
+
underlyingToken,
|
|
452
|
+
chainId: this.config.sourceChainId,
|
|
453
|
+
height: transferReceipt.blockNumber
|
|
454
|
+
};
|
|
455
|
+
}).pipe(Effect.provide(evm.PublicClient.Live({
|
|
456
|
+
chain: options.walletClient.chain,
|
|
457
|
+
transport: http(this.config.sourceRpcUrl)
|
|
458
|
+
})), Effect.provideService(evm.WalletClient, {
|
|
459
|
+
client: options.walletClient,
|
|
460
|
+
account: options.walletClient.account,
|
|
461
|
+
chain: options.walletClient.chain
|
|
462
|
+
}), Effect.runPromise);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Deposit underlying tokens to ZAsset and transfer to deposit address
|
|
466
|
+
*
|
|
467
|
+
* Executes 3 transactions: approve → deposit → transfer.
|
|
468
|
+
* The wallet client must be connected to the source chain.
|
|
469
|
+
*
|
|
470
|
+
* @param secret - The 32-byte secret as a hex string
|
|
471
|
+
* @param beneficiaries - Array of 0-4 beneficiary addresses
|
|
472
|
+
* @param amount - Amount to deposit (in underlying token's smallest unit)
|
|
473
|
+
* @param walletClient - viem WalletClient with account and chain configured
|
|
474
|
+
* @returns Transaction hash, deposit address, underlying token, and chain ID
|
|
475
|
+
*/
|
|
476
|
+
async unsafeDeposit(options) {
|
|
477
|
+
const {
|
|
478
|
+
paymentKey,
|
|
479
|
+
beneficiaries,
|
|
480
|
+
amount,
|
|
481
|
+
walletClient
|
|
482
|
+
} = options;
|
|
483
|
+
if (!walletClient.account) {
|
|
484
|
+
throw new Error("WalletClient must have an account");
|
|
485
|
+
}
|
|
486
|
+
if (!walletClient.chain) {
|
|
487
|
+
throw new Error("WalletClient must have a chain configured");
|
|
488
|
+
}
|
|
489
|
+
const depositAddress = this.getDepositAddress(paymentKey, beneficiaries);
|
|
490
|
+
const publicClient = createPublicClient({
|
|
491
|
+
chain: walletClient.chain,
|
|
492
|
+
transport: http(this.config.sourceRpcUrl)
|
|
493
|
+
});
|
|
494
|
+
// Get underlying token address
|
|
495
|
+
const underlyingToken = await this.srcClient.getClient().readContract({
|
|
496
|
+
address: this.config.srcZAssetAddress,
|
|
497
|
+
abi: [{
|
|
498
|
+
inputs: [],
|
|
499
|
+
name: "underlying",
|
|
500
|
+
outputs: [{
|
|
501
|
+
name: "",
|
|
502
|
+
type: "address"
|
|
503
|
+
}],
|
|
504
|
+
stateMutability: "view",
|
|
505
|
+
type: "function"
|
|
506
|
+
}],
|
|
507
|
+
functionName: "underlying"
|
|
508
|
+
});
|
|
509
|
+
if (underlyingToken === ZERO_ADDRESS) {
|
|
510
|
+
throw new Error("ZAsset is not a wrapped token (underlying is zero address)");
|
|
511
|
+
}
|
|
512
|
+
const ERC20_ABI = [{
|
|
513
|
+
inputs: [{
|
|
514
|
+
name: "spender",
|
|
515
|
+
type: "address"
|
|
516
|
+
}, {
|
|
517
|
+
name: "amount",
|
|
518
|
+
type: "uint256"
|
|
519
|
+
}],
|
|
520
|
+
name: "approve",
|
|
521
|
+
outputs: [{
|
|
522
|
+
name: "",
|
|
523
|
+
type: "bool"
|
|
524
|
+
}],
|
|
525
|
+
stateMutability: "nonpayable",
|
|
526
|
+
type: "function"
|
|
527
|
+
}];
|
|
528
|
+
const ZASSET_ABI = [{
|
|
529
|
+
inputs: [{
|
|
530
|
+
name: "amount",
|
|
531
|
+
type: "uint256"
|
|
532
|
+
}],
|
|
533
|
+
name: "deposit",
|
|
534
|
+
outputs: [],
|
|
535
|
+
stateMutability: "nonpayable",
|
|
536
|
+
type: "function"
|
|
537
|
+
}, {
|
|
538
|
+
inputs: [{
|
|
539
|
+
name: "to",
|
|
540
|
+
type: "address"
|
|
541
|
+
}, {
|
|
542
|
+
name: "amount",
|
|
543
|
+
type: "uint256"
|
|
544
|
+
}],
|
|
545
|
+
name: "transfer",
|
|
546
|
+
outputs: [{
|
|
547
|
+
name: "",
|
|
548
|
+
type: "bool"
|
|
549
|
+
}],
|
|
550
|
+
stateMutability: "nonpayable",
|
|
551
|
+
type: "function"
|
|
552
|
+
}];
|
|
553
|
+
// 1. Approve ZAsset to spend underlying tokens
|
|
554
|
+
const approveHash = await walletClient.writeContractSync({
|
|
555
|
+
address: underlyingToken,
|
|
556
|
+
abi: ERC20_ABI,
|
|
557
|
+
functionName: "approve",
|
|
558
|
+
args: [this.config.srcZAssetAddress, amount],
|
|
559
|
+
chain: walletClient.chain,
|
|
560
|
+
account: walletClient.account
|
|
561
|
+
}).then(x => x.transactionHash);
|
|
562
|
+
const approveReceipt = await publicClient.waitForTransactionReceipt({
|
|
563
|
+
hash: approveHash
|
|
564
|
+
});
|
|
565
|
+
if (approveReceipt.status === "reverted") {
|
|
566
|
+
throw new Error(`Approve transaction reverted: ${approveHash}`);
|
|
567
|
+
}
|
|
568
|
+
// 2. Deposit underlying to get ZAsset
|
|
569
|
+
const depositHash = await walletClient.writeContractSync({
|
|
570
|
+
address: this.config.srcZAssetAddress,
|
|
571
|
+
abi: ZASSET_ABI,
|
|
572
|
+
functionName: "deposit",
|
|
573
|
+
args: [amount],
|
|
574
|
+
chain: walletClient.chain,
|
|
575
|
+
account: walletClient.account
|
|
576
|
+
}).then(x => x.transactionHash);
|
|
577
|
+
const depositReceipt = await publicClient.waitForTransactionReceipt({
|
|
578
|
+
hash: depositHash
|
|
579
|
+
});
|
|
580
|
+
if (depositReceipt.status === "reverted") {
|
|
581
|
+
throw new Error(`Deposit transaction reverted: ${depositHash}`);
|
|
582
|
+
}
|
|
583
|
+
// 3. Transfer ZAsset to deposit address
|
|
584
|
+
const transferHash = await walletClient.writeContractSync({
|
|
585
|
+
address: this.config.srcZAssetAddress,
|
|
586
|
+
abi: ZASSET_ABI,
|
|
587
|
+
functionName: "transfer",
|
|
588
|
+
args: [depositAddress, amount],
|
|
589
|
+
chain: walletClient.chain,
|
|
590
|
+
account: walletClient.account
|
|
591
|
+
}).then(x => x.transactionHash);
|
|
592
|
+
const transferReceipt = await publicClient.waitForTransactionReceipt({
|
|
593
|
+
hash: transferHash
|
|
594
|
+
});
|
|
595
|
+
if (transferReceipt.status === "reverted") {
|
|
596
|
+
throw new Error(`Transfer transaction reverted: ${transferHash}`);
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
txHash: transferHash,
|
|
600
|
+
depositAddress,
|
|
601
|
+
underlyingToken,
|
|
602
|
+
chainId: this.config.sourceChainId,
|
|
603
|
+
height: transferReceipt.blockNumber
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Update loopback light client to a specific block height
|
|
608
|
+
*
|
|
609
|
+
* Fetches the IBC handler address internally from the destination ZAsset.
|
|
610
|
+
* The wallet client must be connected to the destination chain.
|
|
611
|
+
*
|
|
612
|
+
* @returns Transaction hash, block number, state root, and chain ID
|
|
613
|
+
*/
|
|
614
|
+
async updateLightClient(options) {
|
|
615
|
+
const {
|
|
616
|
+
clientId,
|
|
617
|
+
height,
|
|
618
|
+
walletClient
|
|
619
|
+
} = options;
|
|
620
|
+
if (!walletClient.account) {
|
|
621
|
+
throw new Error("WalletClient must have an account");
|
|
622
|
+
}
|
|
623
|
+
if (!walletClient.chain) {
|
|
624
|
+
throw new Error("WalletClient must have a chain configured");
|
|
625
|
+
}
|
|
626
|
+
return Effect.gen(this, function* () {
|
|
627
|
+
const publicClient = createPublicClient({
|
|
628
|
+
chain: walletClient.chain,
|
|
629
|
+
transport: http(this.config.destinationRpcUrl)
|
|
630
|
+
});
|
|
631
|
+
// Get IBC handler address from ZAsset
|
|
632
|
+
const ibcHandlerAddress = yield* Effect.tryPromise({
|
|
633
|
+
try: () => this.dstClient.getIbcHandlerAddress(this.config.dstZAssetAddress),
|
|
634
|
+
catch: cause => Effect.fail(cause)
|
|
635
|
+
});
|
|
636
|
+
// Get the block number
|
|
637
|
+
const blockNumber = height === "latest" ? yield* pipe(Effect.tryPromise(() => publicClient.getBlockNumber()), Effect.retry(commonRetry)) : height;
|
|
638
|
+
// Get the block for metadata
|
|
639
|
+
const block = yield* pipe(Effect.tryPromise(() => publicClient.getBlock({
|
|
640
|
+
blockNumber
|
|
641
|
+
})), Effect.retry(commonRetry));
|
|
642
|
+
if (!block.number) {
|
|
643
|
+
throw new Error("Block number is null");
|
|
644
|
+
}
|
|
645
|
+
// Helper to convert bigint/number to minimal RLP hex encoding
|
|
646
|
+
const toRlpHex = value => {
|
|
647
|
+
if (value === undefined || value === null || value === 0n || value === 0) {
|
|
648
|
+
return "0x";
|
|
649
|
+
}
|
|
650
|
+
let hex = typeof value === "bigint" ? value.toString(16) : value.toString(16);
|
|
651
|
+
if (hex.length % 2 !== 0) {
|
|
652
|
+
hex = "0" + hex;
|
|
653
|
+
}
|
|
654
|
+
return `0x${hex}`;
|
|
655
|
+
};
|
|
656
|
+
// Build header fields in order (pre-merge + post-merge fields)
|
|
657
|
+
const headerFields = [block.parentHash, block.sha3Uncles, block.miner, block.stateRoot, block.transactionsRoot, block.receiptsRoot, block.logsBloom ?? "0x" + "00".repeat(256), toRlpHex(block.difficulty), toRlpHex(block.number), toRlpHex(block.gasLimit), toRlpHex(block.gasUsed), toRlpHex(block.timestamp), block.extraData, block.mixHash ?? "0x0000000000000000000000000000000000000000000000000000000000000000", block.nonce ?? "0x0000000000000000"];
|
|
658
|
+
// Post-merge fields
|
|
659
|
+
if (block.baseFeePerGas !== undefined && block.baseFeePerGas !== null) {
|
|
660
|
+
headerFields.push(toRlpHex(block.baseFeePerGas));
|
|
661
|
+
}
|
|
662
|
+
if (block.withdrawalsRoot) {
|
|
663
|
+
headerFields.push(block.withdrawalsRoot);
|
|
664
|
+
}
|
|
665
|
+
if (block.blobGasUsed !== undefined && block.blobGasUsed !== null) {
|
|
666
|
+
headerFields.push(toRlpHex(block.blobGasUsed));
|
|
667
|
+
}
|
|
668
|
+
if (block.excessBlobGas !== undefined && block.excessBlobGas !== null) {
|
|
669
|
+
headerFields.push(toRlpHex(block.excessBlobGas));
|
|
670
|
+
}
|
|
671
|
+
if (block.parentBeaconBlockRoot) {
|
|
672
|
+
headerFields.push(block.parentBeaconBlockRoot);
|
|
673
|
+
}
|
|
674
|
+
const blockAny = block;
|
|
675
|
+
if (blockAny.requestsHash) {
|
|
676
|
+
headerFields.push(blockAny.requestsHash);
|
|
677
|
+
}
|
|
678
|
+
const rlpEncoded = toRlp(headerFields);
|
|
679
|
+
// Verify the encoding produces the correct block hash
|
|
680
|
+
const computedHash = keccak256(rlpEncoded);
|
|
681
|
+
if (computedHash !== block.hash) {
|
|
682
|
+
throw new Error(`RLP encoding mismatch: computed hash ${computedHash} does not match block hash ${block.hash}`);
|
|
683
|
+
}
|
|
684
|
+
// Encode the Header struct: (uint64 height, bytes encodedHeader)
|
|
685
|
+
const clientMessage = encodeAbiParameters([{
|
|
686
|
+
type: "uint64",
|
|
687
|
+
name: "height"
|
|
688
|
+
}, {
|
|
689
|
+
type: "bytes",
|
|
690
|
+
name: "encodedHeader"
|
|
691
|
+
}], [block.number, rlpEncoded]);
|
|
692
|
+
const LIGHTCLIENT_ABI = [{
|
|
693
|
+
inputs: [{
|
|
694
|
+
name: "caller",
|
|
695
|
+
type: "address"
|
|
696
|
+
}, {
|
|
697
|
+
name: "clientId",
|
|
698
|
+
type: "uint32"
|
|
699
|
+
}, {
|
|
700
|
+
name: "clientMessage",
|
|
701
|
+
type: "bytes"
|
|
702
|
+
}, {
|
|
703
|
+
name: "relayer",
|
|
704
|
+
type: "address"
|
|
705
|
+
}],
|
|
706
|
+
name: "updateClient",
|
|
707
|
+
outputs: [],
|
|
708
|
+
stateMutability: "nonpayable",
|
|
709
|
+
type: "function"
|
|
710
|
+
}];
|
|
711
|
+
// Wait for the next block so the fetched block's hash is verifiable on-chain
|
|
712
|
+
let currentBlock = yield* pipe(Effect.tryPromise(() => publicClient.getBlockNumber()), Effect.retry(commonRetry));
|
|
713
|
+
while (currentBlock <= blockNumber) {
|
|
714
|
+
yield* Effect.sleep("1 second");
|
|
715
|
+
currentBlock = yield* Effect.tryPromise(() => publicClient.getBlockNumber());
|
|
716
|
+
}
|
|
717
|
+
const loopbackClient = yield* pipe(evm.readContract({
|
|
718
|
+
address: ibcHandlerAddress,
|
|
719
|
+
abi: IBC_STORE_ABI,
|
|
720
|
+
functionName: "getClient",
|
|
721
|
+
args: [clientId]
|
|
722
|
+
}), Effect.retry(commonRetry));
|
|
723
|
+
// Submit the transaction
|
|
724
|
+
const txHash = yield* pipe(evm.writeContract({
|
|
725
|
+
address: loopbackClient,
|
|
726
|
+
abi: LIGHTCLIENT_ABI,
|
|
727
|
+
functionName: "updateClient",
|
|
728
|
+
args: [walletClient.account.address, clientId, clientMessage, walletClient.account.address],
|
|
729
|
+
chain: walletClient.chain,
|
|
730
|
+
account: walletClient.account
|
|
731
|
+
}), Effect.retry(commonRetry));
|
|
732
|
+
const chainId = yield* Effect.sync(() => this.config.destinationChainId);
|
|
733
|
+
return {
|
|
734
|
+
txHash,
|
|
735
|
+
blockNumber: block.number,
|
|
736
|
+
stateRoot: block.stateRoot,
|
|
737
|
+
chainId
|
|
738
|
+
};
|
|
739
|
+
}).pipe(Effect.provide(evm.PublicClient.Live({
|
|
740
|
+
chain: walletClient.chain,
|
|
741
|
+
transport: http(this.config.destinationRpcUrl)
|
|
742
|
+
})), Effect.provideService(evm.WalletClient, {
|
|
743
|
+
client: options.walletClient,
|
|
744
|
+
account: options.walletClient.account,
|
|
745
|
+
chain: options.walletClient.chain
|
|
746
|
+
}), Effect.runPromise);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Update loopback light client to a specific block height
|
|
750
|
+
*
|
|
751
|
+
* Fetches the IBC handler address internally from the destination ZAsset.
|
|
752
|
+
* The wallet client must be connected to the destination chain.
|
|
753
|
+
*
|
|
754
|
+
* @returns Transaction hash, block number, state root, and chain ID
|
|
755
|
+
*/
|
|
756
|
+
async unsafeUpdateLightClient(options) {
|
|
757
|
+
const {
|
|
758
|
+
clientId,
|
|
759
|
+
height,
|
|
760
|
+
walletClient
|
|
761
|
+
} = options;
|
|
762
|
+
if (!walletClient.account) {
|
|
763
|
+
throw new Error("WalletClient must have an account");
|
|
764
|
+
}
|
|
765
|
+
if (!walletClient.chain) {
|
|
766
|
+
throw new Error("WalletClient must have a chain configured");
|
|
767
|
+
}
|
|
768
|
+
const publicClient = createPublicClient({
|
|
769
|
+
chain: walletClient.chain,
|
|
770
|
+
transport: http(this.config.destinationRpcUrl)
|
|
771
|
+
});
|
|
772
|
+
// Get IBC handler address from ZAsset
|
|
773
|
+
const ibcHandlerAddress = await this.dstClient.getIbcHandlerAddress(this.config.dstZAssetAddress);
|
|
774
|
+
// Get the block number
|
|
775
|
+
const blockNumber = height === "latest" ? await publicClient.getBlockNumber() : height;
|
|
776
|
+
// Get the block for metadata
|
|
777
|
+
const block = await publicClient.getBlock({
|
|
778
|
+
blockNumber
|
|
779
|
+
});
|
|
780
|
+
if (!block.number) {
|
|
781
|
+
throw new Error("Block number is null");
|
|
782
|
+
}
|
|
783
|
+
// Helper to convert bigint/number to minimal RLP hex encoding
|
|
784
|
+
const toRlpHex = value => {
|
|
785
|
+
if (value === undefined || value === null || value === 0n || value === 0) {
|
|
786
|
+
return "0x";
|
|
787
|
+
}
|
|
788
|
+
let hex = typeof value === "bigint" ? value.toString(16) : value.toString(16);
|
|
789
|
+
if (hex.length % 2 !== 0) {
|
|
790
|
+
hex = "0" + hex;
|
|
791
|
+
}
|
|
792
|
+
return `0x${hex}`;
|
|
793
|
+
};
|
|
794
|
+
// Build header fields in order (pre-merge + post-merge fields)
|
|
795
|
+
const headerFields = [block.parentHash, block.sha3Uncles, block.miner, block.stateRoot, block.transactionsRoot, block.receiptsRoot, block.logsBloom ?? "0x" + "00".repeat(256), toRlpHex(block.difficulty), toRlpHex(block.number), toRlpHex(block.gasLimit), toRlpHex(block.gasUsed), toRlpHex(block.timestamp), block.extraData, block.mixHash ?? "0x0000000000000000000000000000000000000000000000000000000000000000", block.nonce ?? "0x0000000000000000"];
|
|
796
|
+
// Post-merge fields
|
|
797
|
+
if (block.baseFeePerGas !== undefined && block.baseFeePerGas !== null) {
|
|
798
|
+
headerFields.push(toRlpHex(block.baseFeePerGas));
|
|
799
|
+
}
|
|
800
|
+
if (block.withdrawalsRoot) {
|
|
801
|
+
headerFields.push(block.withdrawalsRoot);
|
|
802
|
+
}
|
|
803
|
+
if (block.blobGasUsed !== undefined && block.blobGasUsed !== null) {
|
|
804
|
+
headerFields.push(toRlpHex(block.blobGasUsed));
|
|
805
|
+
}
|
|
806
|
+
if (block.excessBlobGas !== undefined && block.excessBlobGas !== null) {
|
|
807
|
+
headerFields.push(toRlpHex(block.excessBlobGas));
|
|
808
|
+
}
|
|
809
|
+
if (block.parentBeaconBlockRoot) {
|
|
810
|
+
headerFields.push(block.parentBeaconBlockRoot);
|
|
811
|
+
}
|
|
812
|
+
const blockAny = block;
|
|
813
|
+
if (blockAny.requestsHash) {
|
|
814
|
+
headerFields.push(blockAny.requestsHash);
|
|
815
|
+
}
|
|
816
|
+
const rlpEncoded = toRlp(headerFields);
|
|
817
|
+
// Verify the encoding produces the correct block hash
|
|
818
|
+
const computedHash = keccak256(rlpEncoded);
|
|
819
|
+
if (computedHash !== block.hash) {
|
|
820
|
+
throw new Error(`RLP encoding mismatch: computed hash ${computedHash} does not match block hash ${block.hash}`);
|
|
821
|
+
}
|
|
822
|
+
// Encode the Header struct: (uint64 height, bytes encodedHeader)
|
|
823
|
+
const clientMessage = encodeAbiParameters([{
|
|
824
|
+
type: "uint64",
|
|
825
|
+
name: "height"
|
|
826
|
+
}, {
|
|
827
|
+
type: "bytes",
|
|
828
|
+
name: "encodedHeader"
|
|
829
|
+
}], [block.number, rlpEncoded]);
|
|
830
|
+
const LIGHTCLIENT_ABI = [{
|
|
831
|
+
inputs: [{
|
|
832
|
+
name: "caller",
|
|
833
|
+
type: "address"
|
|
834
|
+
}, {
|
|
835
|
+
name: "clientId",
|
|
836
|
+
type: "uint32"
|
|
837
|
+
}, {
|
|
838
|
+
name: "clientMessage",
|
|
839
|
+
type: "bytes"
|
|
840
|
+
}, {
|
|
841
|
+
name: "relayer",
|
|
842
|
+
type: "address"
|
|
843
|
+
}],
|
|
844
|
+
name: "updateClient",
|
|
845
|
+
outputs: [],
|
|
846
|
+
stateMutability: "nonpayable",
|
|
847
|
+
type: "function"
|
|
848
|
+
}];
|
|
849
|
+
// Wait for the next block so the fetched block's hash is verifiable on-chain
|
|
850
|
+
let currentBlock = await publicClient.getBlockNumber();
|
|
851
|
+
while (currentBlock <= blockNumber) {
|
|
852
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
853
|
+
currentBlock = await publicClient.getBlockNumber();
|
|
854
|
+
}
|
|
855
|
+
const loopbackClient = await publicClient.readContract({
|
|
856
|
+
address: ibcHandlerAddress,
|
|
857
|
+
abi: IBC_STORE_ABI,
|
|
858
|
+
functionName: "getClient",
|
|
859
|
+
args: [clientId]
|
|
860
|
+
});
|
|
861
|
+
// Submit the transaction
|
|
862
|
+
const txHash = await walletClient.writeContractSync({
|
|
863
|
+
address: loopbackClient,
|
|
864
|
+
abi: LIGHTCLIENT_ABI,
|
|
865
|
+
functionName: "updateClient",
|
|
866
|
+
args: [walletClient.account.address, clientId, clientMessage, walletClient.account.address],
|
|
867
|
+
chain: walletClient.chain,
|
|
868
|
+
account: walletClient.account
|
|
869
|
+
}).then(x => x.transactionHash);
|
|
870
|
+
return {
|
|
871
|
+
txHash,
|
|
872
|
+
blockNumber: block.number,
|
|
873
|
+
stateRoot: block.stateRoot,
|
|
874
|
+
chainId: this.config.destinationChainId
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Get redemption history for a secret
|
|
879
|
+
*
|
|
880
|
+
* Queries the Redeemed events filtered by the nullifier derived from the secret.
|
|
881
|
+
* This is a read-only operation that doesn't require a wallet.
|
|
882
|
+
*
|
|
883
|
+
* @param secret - The 32-byte secret as a hex string
|
|
884
|
+
* @param fromBlock - Start block number (default: earliest)
|
|
885
|
+
* @param toBlock - End block number (default: latest)
|
|
886
|
+
* @returns Array of redemption transactions
|
|
887
|
+
*/
|
|
888
|
+
getRedemptionHistory(secret, fromBlock, toBlock) {
|
|
889
|
+
return Effect.gen(this, function* () {
|
|
890
|
+
const nullifier = this.getNullifier(secret);
|
|
891
|
+
return yield* pipe(Effect.tryPromise(() => this.dstClient.getRedemptionHistory(this.config.dstZAssetAddress, nullifier, fromBlock, toBlock)), Effect.retry(commonRetry));
|
|
892
|
+
}).pipe(Effect.runPromise);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Get redemption history for a secret
|
|
896
|
+
*
|
|
897
|
+
* Queries the Redeemed events filtered by the nullifier derived from the secret.
|
|
898
|
+
* This is a read-only operation that doesn't require a wallet.
|
|
899
|
+
*
|
|
900
|
+
* @param secret - The 32-byte secret as a hex string
|
|
901
|
+
* @param fromBlock - Start block number (default: earliest)
|
|
902
|
+
* @param toBlock - End block number (default: latest)
|
|
903
|
+
* @returns Array of redemption transactions
|
|
904
|
+
*/
|
|
905
|
+
async unsafeGetRedemptionHistory(secret, fromBlock, toBlock) {
|
|
906
|
+
const nullifier = this.getNullifier(secret);
|
|
907
|
+
return this.dstClient.getRedemptionHistory(this.config.dstZAssetAddress, nullifier, fromBlock, toBlock);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Full redeem flow: generate proof + get attestation + submit transaction
|
|
911
|
+
*
|
|
912
|
+
* This method orchestrates the entire redemption process:
|
|
913
|
+
* 1. Generates a ZK proof via the prover server
|
|
914
|
+
* 2. Gets attestation from the attestation service
|
|
915
|
+
* 3. Submits the redeem transaction
|
|
916
|
+
*
|
|
917
|
+
* The wallet client must be connected to the destination chain.
|
|
918
|
+
*
|
|
919
|
+
* @returns Transaction hash, proof, and metadata
|
|
920
|
+
*/
|
|
921
|
+
redeem(options) {
|
|
922
|
+
const {
|
|
923
|
+
paymentKey,
|
|
924
|
+
beneficiaries,
|
|
925
|
+
beneficiary,
|
|
926
|
+
amount,
|
|
927
|
+
clientIds,
|
|
928
|
+
selectedClientId,
|
|
929
|
+
walletClient,
|
|
930
|
+
unwrap = true
|
|
931
|
+
} = options;
|
|
932
|
+
// Resolve attestation URL and API key from options or stored config
|
|
933
|
+
const attestationUrl = this.config.attestorUrl;
|
|
934
|
+
const attestorApiKey = this.config.attestorApiKey;
|
|
935
|
+
if (!attestationUrl) {
|
|
936
|
+
throw Error("Attestation URL must be provided either in redeem options or ClientOptions");
|
|
937
|
+
}
|
|
938
|
+
if (!attestorApiKey) {
|
|
939
|
+
throw Error("Attestation API key must be provided either in redeem options or ClientOptions");
|
|
940
|
+
}
|
|
941
|
+
if (!walletClient.account) {
|
|
942
|
+
throw Error("WalletClient must have an account");
|
|
943
|
+
}
|
|
944
|
+
if (!walletClient.chain) {
|
|
945
|
+
throw Error("WalletClient must have a chain configured");
|
|
946
|
+
}
|
|
947
|
+
return Effect.gen(this, function* () {
|
|
948
|
+
// 1. Generate proof
|
|
949
|
+
const proofResult = yield* Effect.tryPromise({
|
|
950
|
+
try: () => this.generateProof(paymentKey, beneficiaries, beneficiary, amount, clientIds, selectedClientId),
|
|
951
|
+
catch: cause => Effect.fail(cause)
|
|
952
|
+
}).pipe(Effect.retry(commonRetry));
|
|
953
|
+
if (!proofResult.proof.success || !proofResult.proof.proofJson || !proofResult.metadata) {
|
|
954
|
+
return yield* Effect.fail(Error(proofResult.proof.error ?? "Proof generation failed"));
|
|
955
|
+
}
|
|
956
|
+
const proofJson = parseProofJson(proofResult.proof.proofJson);
|
|
957
|
+
const metadata = proofResult.metadata;
|
|
958
|
+
const depositAddress = this.getDepositAddress(paymentKey, beneficiaries);
|
|
959
|
+
// 2. Get attestation
|
|
960
|
+
const attestationClient = new AttestationClient(attestationUrl, attestorApiKey);
|
|
961
|
+
const attestationResponse = yield* Effect.tryPromise({
|
|
962
|
+
try: () => attestationClient.getAttestation(depositAddress, beneficiary),
|
|
963
|
+
catch: cause => Effect.fail(cause)
|
|
964
|
+
}).pipe(Effect.retry(commonRetry));
|
|
965
|
+
// 3. Build redeem params
|
|
966
|
+
const redeemParams = proofJsonToRedeemParams(proofJson, {
|
|
967
|
+
lightClients: metadata.lightClients,
|
|
968
|
+
nullifier: metadata.nullifier,
|
|
969
|
+
value: metadata.value,
|
|
970
|
+
beneficiary: metadata.beneficiary,
|
|
971
|
+
attestedMessage: attestationResponse.attestedMessage,
|
|
972
|
+
signature: attestationResponse.signature
|
|
973
|
+
});
|
|
974
|
+
// 4. Submit transaction
|
|
975
|
+
const ZASSET_ABI = [{
|
|
976
|
+
inputs: [{
|
|
977
|
+
name: "proof",
|
|
978
|
+
type: "uint256[8]"
|
|
979
|
+
}, {
|
|
980
|
+
name: "commitments",
|
|
981
|
+
type: "uint256[2]"
|
|
982
|
+
}, {
|
|
983
|
+
name: "commitmentPok",
|
|
984
|
+
type: "uint256[2]"
|
|
985
|
+
}, {
|
|
986
|
+
name: "lightClients",
|
|
987
|
+
type: "tuple[]",
|
|
988
|
+
components: [{
|
|
989
|
+
name: "clientId",
|
|
990
|
+
type: "uint32"
|
|
991
|
+
}, {
|
|
992
|
+
name: "height",
|
|
993
|
+
type: "uint64"
|
|
994
|
+
}]
|
|
995
|
+
}, {
|
|
996
|
+
name: "nullifier",
|
|
997
|
+
type: "uint256"
|
|
998
|
+
}, {
|
|
999
|
+
name: "value",
|
|
1000
|
+
type: "uint256"
|
|
1001
|
+
}, {
|
|
1002
|
+
name: "beneficiary",
|
|
1003
|
+
type: "address"
|
|
1004
|
+
}, {
|
|
1005
|
+
name: "attestedMessage",
|
|
1006
|
+
type: "bytes32"
|
|
1007
|
+
}, {
|
|
1008
|
+
name: "signature",
|
|
1009
|
+
type: "bytes"
|
|
1010
|
+
}, {
|
|
1011
|
+
name: "unwrap",
|
|
1012
|
+
type: "bool"
|
|
1013
|
+
}],
|
|
1014
|
+
name: "redeem",
|
|
1015
|
+
outputs: [],
|
|
1016
|
+
stateMutability: "nonpayable",
|
|
1017
|
+
type: "function"
|
|
1018
|
+
}];
|
|
1019
|
+
const txHash = yield* pipe(evm.writeContract({
|
|
1020
|
+
address: this.config.dstZAssetAddress,
|
|
1021
|
+
abi: ZASSET_ABI,
|
|
1022
|
+
functionName: "redeem",
|
|
1023
|
+
args: [redeemParams.proof, redeemParams.commitments, redeemParams.commitmentPok, redeemParams.lightClients, redeemParams.nullifier, redeemParams.value, redeemParams.beneficiary, redeemParams.attestedMessage, redeemParams.signature, unwrap],
|
|
1024
|
+
chain: walletClient.chain,
|
|
1025
|
+
account: walletClient.account
|
|
1026
|
+
}), Effect.retry(commonRetry));
|
|
1027
|
+
return {
|
|
1028
|
+
txHash,
|
|
1029
|
+
proof: proofJson,
|
|
1030
|
+
metadata
|
|
1031
|
+
};
|
|
1032
|
+
}).pipe(Effect.provide(evm.PublicClient.Live({
|
|
1033
|
+
chain: options.walletClient.chain,
|
|
1034
|
+
transport: http(this.config.destinationRpcUrl)
|
|
1035
|
+
})), Effect.provideService(evm.WalletClient, {
|
|
1036
|
+
client: options.walletClient,
|
|
1037
|
+
account: options.walletClient.account,
|
|
1038
|
+
chain: options.walletClient.chain
|
|
1039
|
+
}), Effect.runPromise);
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Full redeem flow: generate proof + get attestation + submit transaction
|
|
1043
|
+
*
|
|
1044
|
+
* This method orchestrates the entire redemption process:
|
|
1045
|
+
* 1. Generates a ZK proof via the prover server
|
|
1046
|
+
* 2. Gets attestation from the attestation service
|
|
1047
|
+
* 3. Submits the redeem transaction
|
|
1048
|
+
*
|
|
1049
|
+
* The wallet client must be connected to the destination chain.
|
|
1050
|
+
*
|
|
1051
|
+
* @returns Transaction hash, proof, and metadata
|
|
1052
|
+
*/
|
|
1053
|
+
async unsafeRedeem(options) {
|
|
1054
|
+
const {
|
|
1055
|
+
paymentKey,
|
|
1056
|
+
beneficiaries,
|
|
1057
|
+
beneficiary,
|
|
1058
|
+
amount,
|
|
1059
|
+
clientIds,
|
|
1060
|
+
selectedClientId,
|
|
1061
|
+
walletClient,
|
|
1062
|
+
unwrap = true
|
|
1063
|
+
} = options;
|
|
1064
|
+
// Resolve attestation URL and API key from options or stored config
|
|
1065
|
+
const attestationUrl = this.config.attestorUrl;
|
|
1066
|
+
const attestorApiKey = this.config.attestorApiKey;
|
|
1067
|
+
if (!attestationUrl) {
|
|
1068
|
+
throw new Error("Attestation URL must be provided either in redeem options or ClientOptions");
|
|
1069
|
+
}
|
|
1070
|
+
if (!attestorApiKey) {
|
|
1071
|
+
throw new Error("Attestation API key must be provided either in redeem options or ClientOptions");
|
|
1072
|
+
}
|
|
1073
|
+
if (!walletClient.account) {
|
|
1074
|
+
throw new Error("WalletClient must have an account");
|
|
1075
|
+
}
|
|
1076
|
+
if (!walletClient.chain) {
|
|
1077
|
+
throw new Error("WalletClient must have a chain configured");
|
|
1078
|
+
}
|
|
1079
|
+
// 1. Generate proof
|
|
1080
|
+
const proofResult = await this.generateProof(paymentKey, beneficiaries, beneficiary, amount, clientIds, selectedClientId);
|
|
1081
|
+
if (!proofResult.proof.success || !proofResult.proof.proofJson || !proofResult.metadata) {
|
|
1082
|
+
throw new Error(proofResult.proof.error ?? "Proof generation failed");
|
|
1083
|
+
}
|
|
1084
|
+
const proofJson = parseProofJson(proofResult.proof.proofJson);
|
|
1085
|
+
const metadata = proofResult.metadata;
|
|
1086
|
+
const depositAddress = this.getDepositAddress(paymentKey, beneficiaries);
|
|
1087
|
+
// 2. Get attestation
|
|
1088
|
+
const attestationClient = new AttestationClient(attestationUrl, attestorApiKey);
|
|
1089
|
+
const attestationResponse = await attestationClient.getAttestation(depositAddress, beneficiary);
|
|
1090
|
+
// 3. Build redeem params
|
|
1091
|
+
const redeemParams = proofJsonToRedeemParams(proofJson, {
|
|
1092
|
+
lightClients: metadata.lightClients,
|
|
1093
|
+
nullifier: metadata.nullifier,
|
|
1094
|
+
value: metadata.value,
|
|
1095
|
+
beneficiary: metadata.beneficiary,
|
|
1096
|
+
attestedMessage: attestationResponse.attestedMessage,
|
|
1097
|
+
signature: attestationResponse.signature
|
|
1098
|
+
});
|
|
1099
|
+
// 4. Submit transaction
|
|
1100
|
+
const publicClient = createPublicClient({
|
|
1101
|
+
chain: walletClient.chain,
|
|
1102
|
+
transport: http(this.config.destinationRpcUrl)
|
|
1103
|
+
});
|
|
1104
|
+
const ZASSET_ABI = [{
|
|
1105
|
+
inputs: [{
|
|
1106
|
+
name: "proof",
|
|
1107
|
+
type: "uint256[8]"
|
|
1108
|
+
}, {
|
|
1109
|
+
name: "commitments",
|
|
1110
|
+
type: "uint256[2]"
|
|
1111
|
+
}, {
|
|
1112
|
+
name: "commitmentPok",
|
|
1113
|
+
type: "uint256[2]"
|
|
1114
|
+
}, {
|
|
1115
|
+
name: "lightClients",
|
|
1116
|
+
type: "tuple[]",
|
|
1117
|
+
components: [{
|
|
1118
|
+
name: "clientId",
|
|
1119
|
+
type: "uint32"
|
|
1120
|
+
}, {
|
|
1121
|
+
name: "height",
|
|
1122
|
+
type: "uint64"
|
|
1123
|
+
}]
|
|
1124
|
+
}, {
|
|
1125
|
+
name: "nullifier",
|
|
1126
|
+
type: "uint256"
|
|
1127
|
+
}, {
|
|
1128
|
+
name: "value",
|
|
1129
|
+
type: "uint256"
|
|
1130
|
+
}, {
|
|
1131
|
+
name: "beneficiary",
|
|
1132
|
+
type: "address"
|
|
1133
|
+
}, {
|
|
1134
|
+
name: "attestedMessage",
|
|
1135
|
+
type: "bytes32"
|
|
1136
|
+
}, {
|
|
1137
|
+
name: "signature",
|
|
1138
|
+
type: "bytes"
|
|
1139
|
+
}, {
|
|
1140
|
+
name: "unwrap",
|
|
1141
|
+
type: "bool"
|
|
1142
|
+
}],
|
|
1143
|
+
name: "redeem",
|
|
1144
|
+
outputs: [],
|
|
1145
|
+
stateMutability: "nonpayable",
|
|
1146
|
+
type: "function"
|
|
1147
|
+
}];
|
|
1148
|
+
const txHash = await walletClient.writeContractSync({
|
|
1149
|
+
address: this.config.dstZAssetAddress,
|
|
1150
|
+
abi: ZASSET_ABI,
|
|
1151
|
+
functionName: "redeem",
|
|
1152
|
+
args: [redeemParams.proof, redeemParams.commitments, redeemParams.commitmentPok, redeemParams.lightClients, redeemParams.nullifier, redeemParams.value, redeemParams.beneficiary, redeemParams.attestedMessage, redeemParams.signature, unwrap],
|
|
1153
|
+
chain: walletClient.chain,
|
|
1154
|
+
account: walletClient.account
|
|
1155
|
+
}).then(x => x.transactionHash);
|
|
1156
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
1157
|
+
hash: txHash
|
|
1158
|
+
});
|
|
1159
|
+
if (receipt.status === "reverted") {
|
|
1160
|
+
throw new Error(`Redeem transaction reverted: ${txHash}`);
|
|
1161
|
+
}
|
|
1162
|
+
return {
|
|
1163
|
+
txHash,
|
|
1164
|
+
proof: proofJson,
|
|
1165
|
+
metadata
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Pad beneficiaries array to exactly 4 addresses
|
|
1170
|
+
* Empty array = unbounded mode (all zeros, any beneficiary allowed)
|
|
1171
|
+
*/
|
|
1172
|
+
padBeneficiaries(beneficiaries) {
|
|
1173
|
+
if (beneficiaries.length > 4) {
|
|
1174
|
+
throw new Error("Maximum 4 beneficiaries allowed");
|
|
1175
|
+
}
|
|
1176
|
+
const padded = [beneficiaries[0] ?? ZERO_ADDRESS, beneficiaries[1] ?? ZERO_ADDRESS, beneficiaries[2] ?? ZERO_ADDRESS, beneficiaries[3] ?? ZERO_ADDRESS];
|
|
1177
|
+
return padded;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Wait for predicate on block height
|
|
1182
|
+
* @public
|
|
1183
|
+
*/
|
|
1184
|
+
export const waitForBlockCondition = (publicClient, predicate, options) => {
|
|
1185
|
+
const timeoutMs = options?.timeoutMs ?? 60_000;
|
|
1186
|
+
return new Promise((resolve, reject) => {
|
|
1187
|
+
let done = false;
|
|
1188
|
+
const unwatch = publicClient.watchBlockNumber({
|
|
1189
|
+
onBlockNumber(blockNumber) {
|
|
1190
|
+
if (done) return;
|
|
1191
|
+
if (predicate(blockNumber)) {
|
|
1192
|
+
done = true;
|
|
1193
|
+
unwatch();
|
|
1194
|
+
resolve(blockNumber);
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
onError(error) {
|
|
1198
|
+
if (done) return;
|
|
1199
|
+
done = true;
|
|
1200
|
+
unwatch();
|
|
1201
|
+
reject(error);
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
setTimeout(() => {
|
|
1205
|
+
if (done) return;
|
|
1206
|
+
done = true;
|
|
1207
|
+
unwatch();
|
|
1208
|
+
reject(new Error("Timed out waiting for block condition"));
|
|
1209
|
+
}, timeoutMs);
|
|
1210
|
+
});
|
|
1211
|
+
};
|
|
1212
|
+
//# sourceMappingURL=client.js.map
|