From 8da193a7b94bd72425e88af150f5be90f29a35a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 13:41:14 +0000 Subject: [PATCH 01/13] docs: design for supporting deposits from CEX (DEFI-2096) Design proposal for onramping ckERC20 (and later ckETH) directly from a centralized exchange withdrawal: per-account tECDSA deposit addresses swept via EIP-7702, so that depositors never need ETH for gas. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 414 +++++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex.md diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md new file mode 100644 index 000000000000..8bdd7cb274dc --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -0,0 +1,414 @@ +--- +id: DEFI-2096 +title: Support deposit from CEX via per-account deposit addresses (EIP-7702 sweeping) +tags: [cketh, ckerc20, minter, deposit, eip-7702] +--- + +# Support deposit from CEX via per-account deposit addresses (EIP-7702 sweeping) + +## Motivation + +Today the only way to deposit ETH or ERC-20 tokens into ckETH/ckERC20 is to call the +helper smart contract (`DepositHelperWithSubaccount.sol`), which forwards the funds to +the minter's single tECDSA address and emits a `ReceivedEthOrErc20` event carrying the +beneficiary IC principal and subaccount. The minter discovers deposits exclusively by +scraping this event (`src/deposit.rs`, `src/eth_logs/`), i.e. attribution of funds to an +IC account relies entirely on the depositor *executing a contract call*. + +A withdrawal from a centralized exchange (CEX) cannot fit through this path: + +* A CEX only performs plain transfers: a bare ERC-20 `transfer(to, value)` (standard + `Transfer` event, no principal) or a native ETH send (no log at all). +* The sender is the exchange's omnibus hot wallet, shared by all its customers, so + sender-based attribution is impossible. +* Ethereum has no memo/data side-channel on plain transfers. + +Consequently a user holding e.g. USDT or USDC on Coinbase/Binance cannot onramp into +ckUSDT/ckUSDC without first withdrawing to a self-custody wallet, funding it with ETH +for gas, and interacting with the helper contract — a prohibitive UX. Funds sent +directly to the minter address today are simply unaccounted, with no recovery path +(see the comment on `EthBalance::eth_balance` in `src/state.rs`). + +The only attribution channel a CEX supports is the **destination address**. This design +therefore gives each IC account a **unique, deterministic deposit address**, controlled +by the minter through threshold ECDSA (the ckBTC model), and uses **EIP-7702** +(live on Ethereum mainnet since the Pectra upgrade, May 2025) to sweep funds from these +addresses to the minter's main address **without pre-funding them with ETH for gas**: +the deposit EOA signs a one-time authorization delegating its code to a minimal sweeper +contract, and the minter's main (funded) address submits the sweep transaction and pays +for gas. + +Target UX: *"I have USDT on a CEX and I want ckUSDT: I paste my deposit address into +the exchange withdrawal form and the tokens automagically appear as ckUSDT."* + +The design is delivered in two phases: + +* **Phase 1 — ckERC20 only** (ckUSDC, ckUSDT, …): deposits are ERC-20 `Transfer`s, + which always emit logs and never execute recipient code, making detection and + crediting straightforward. +* **Phase 2 — ckETH**: native ETH transfers emit no logs and interact badly with + EIP-7702 delegated code under fixed 21'000 gas limits; this phase has additional + design constraints, described separately. + +## Requirements + +### Phase 1 (ckERC20) + +* `R1`: For every IC account `(principal, subaccount)`, the minter returns a unique, + deterministic Ethereum deposit address. Repeated calls return the same address. Two + distinct accounts never share an address, and no deposit address ever equals the + minter's main address or a helper contract address. +* `R2`: If a supported ERC-20 token is transferred to a registered deposit address, and + the transfer is in a finalized block, and the amount is at least the per-token + minimum deposit amount, then the minter mints `amount - deposit_fee` ckERC20 to the + associated IC account, exactly once (deduplication by `(transaction hash, log + index)`, as for helper-based deposits). +* `R3`: If the ERC-20 `Transfer` sender is on the blocklist, no mint occurs; the + deposit is recorded as invalid (same semantics as today's blocked helper deposits). +* `R4`: Transfers below the per-token minimum deposit amount, and transfers of + unsupported ERC-20 tokens, are not credited. No funds are ever burned or destroyed: + they remain at a tECDSA-controlled address and remain recoverable by the minter. +* `R5`: Every credited deposit is eventually swept to the minter's main address. A + sweep failure or delay never affects already-minted balances; sweeps are retried + until confirmed. +* `R6`: A sweep transaction moves funds only to the minter's main address, regardless + of who triggers it. No other destination is reachable through the sweeper delegate. +* `R7`: The per-token `deposit_fee` and minimum deposit amount are configurable + (upgrade argument / NNS proposal) such that fees cover the amortized sweep gas cost. +* `R8`: All new state transitions (address registration, accepted/invalid deposit, + delegation, sweep sent/confirmed) are recorded as audit events, replayable on + upgrade, consistent with the minter's event-sourcing architecture. +* `R9`: The minter dashboard and metrics expose: registered deposit addresses, + credited-but-unswept balances per token, delegation status, and sweep activity. +* `R10`: Withdrawals (ckERC20 → ERC-20 and ckETH → ETH) are unaffected: they continue + to be served from the minter's main address and its existing nonce sequence. + +### Phase 2 (ckETH) + +* `R11`: If the finalized ETH balance of a deposit address exceeds the sum of all + previously credited (minus swept) amounts by at least the minimum ETH deposit + amount, the minter mints the difference minus the deposit fee to the associated + account, exactly once per balance observation (monotone accounting: total credited + never exceeds total received). +* `R12`: A plain ETH transfer sent with a fixed 21'000 gas limit to a deposit address + MUST NOT be permanently locked: it either succeeds (address has no code at transfer + time) or fails on the sender side (funds never leave the exchange). See the + delegation lifecycle decision below. + +## Non-goals + +* **Gasless deposits from self-custody wallets** (EIP-2612 `permit` / Permit2 + sponsoring): a related but different problem — and mainnet USDT does not implement + EIP-2612. Deposit addresses incidentally also cover this use case (a self-custody + wallet can simply `transfer` to the deposit address), which further reduces its + urgency. +* **Deposits from L2s / other chains**: only Ethereum L1 withdrawals are in scope. A + CEX withdrawal on Arbitrum/Base to the deposit address is out of scope (and must be + documented as unsupported). +* **Replacing the helper-contract flow**: the existing flow remains the cheapest path + for power users and is untouched. +* **Automatic discovery without any user/frontend interaction**: crediting is + claim-based (see Design Decisions); a frontend polling on the user's behalf makes + this invisible in practice. +* **Automatic recovery of unsupported-token deposits**: funds remain recoverable + (key-controlled address) but recovery tooling is future work. +* Accepted residual limitations: + * A CEX that batches ETH withdrawals through a contract (internal transactions) + provides no sender information without trace APIs; Phase 2 compliance screening is + therefore weaker for ETH than for ERC-20 (see Phase 2 section). + * During the short window in which a deposit address carries delegated code, a + fixed-21'000-gas ETH transfer to it fails on the sender side (`R12` guarantees no + loss). + +## Design Decisions + +* **Attribution by destination address, not by sender or contract call.** This is the + only mechanism compatible with plain CEX transfers. Chosen over sender-address + registration (CEX hot wallets are shared and unpredictable) and exact-amount + matching (collision-prone, griefable); see Discussed Alternatives. +* **Deposit addresses are tECDSA-derived EOAs, not contracts.** The minter derives one + EOA per IC account using a non-empty ECDSA derivation path (the main address keeps + the empty path `MAIN_DERIVATION_PATH`). Decisive property: *the minter holds the + key*. Funds at a deposit address are never dependent on contract code being correct — + even with no EIP-7702 at all, any balance is recoverable by classically funding the + address with gas and signing a normal transfer. Chosen over CREATE2 counterfactual + forwarder contracts, where funds are controlled by code alone (see Discussed + Alternatives). +* **Sweeping via EIP-7702, gas paid by the minter's main address.** Each deposit EOA + signs (threshold ECDSA) a one-time EIP-7702 authorization delegating its code to a + single immutable, storage-less sweeper delegate whose only capability is *transfer + everything to the minter's main address*. Sweep transactions are type-`0x04` + transactions sent from the funded main address; many deposit addresses are swept in + one transaction. No deposit address ever needs an ETH balance for gas. +* **Sweeps are permissionless-safe.** The delegate's sweep functions are callable by + anyone because the destination is hardcoded (`R6`); a third party triggering a sweep + only donates gas. This removes access-control state from the delegate and lets the + minter batch calls through the canonical, already-deployed + [Multicall3](https://www.multicall3.com/) (`0xcA11bde05977b3631167028862bE2a173976CA11`) + instead of deploying and auditing a custom batcher. +* **Mint on finalized deposit, sweep asynchronously.** The user is credited as soon as + the deposit is finalized and detected; sweeping is pure treasury consolidation, + batched to amortize gas, and never blocks or reverts a mint (`R5`). This decouples + UX latency from gas-optimization policy. +* **Claim-based detection instead of continuous scraping.** Deposits are detected when + a `notify_deposit` endpoint is called for a specific account (by the user, or + transparently by a frontend/timer polling recently active addresses). A targeted + `eth_getLogs` on one token contract filtered by one recipient address is small and + cheap; continuously scraping `Transfer` logs for an unbounded, growing set of + addresses is not. This mirrors ckBTC's `update_balance`. (The existing helper-contract + scraping is unchanged.) +* **Phase 1 delegation is installed lazily, before the first sweep, and is permanent.** + For ERC-20-only crediting, delegated code on the deposit address is harmless + (ERC-20 transfers never execute recipient code), so one authorization per address — + ever — suffices. Phase 2 revisits this for native ETH (see below). +* **Fees are deducted from the minted amount.** A CEX depositor owns no ckETH to pay + gas with, so the sweep cost is recovered as a per-token flat fee subtracted at mint + time (`minted = amount - deposit_fee`), like ckBTC's check fee. Flat and + proposal-configurable rather than oracle-priced, for simplicity and predictability + (`R7`). + +## Implementation + +### Constraints + +* The minter's transaction layer supports only EIP-1559 (type `0x02`) transactions + (`src/tx.rs`, `EIP1559_TX_ID`); EIP-7702 requires adding the type `0x04` + (`SetCode`) transaction and authorization-tuple signing. +* The minter's main address uses the *empty* ECDSA derivation path + (`MAIN_DERIVATION_PATH` in `src/lib.rs`); any per-account path must be non-empty and + collision-free with it. Withdrawals assume a single sequential nonce for the main + address (`src/state/transactions`); sweep transactions originate from the main + address and therefore share that nonce sequence. +* All Ethereum interaction goes through the EVM-RPC canister with multi-provider + threshold consensus (`src/eth_rpc_client/`); every new call (`eth_getLogs` per + deposit address, `eth_getBalance`, `eth_getTransactionCount` for deposit EOAs) must + use the same reduction strategies. +* The minter is event-sourced (`src/state/audit.rs`, `src/state/event.rs`): all new + state must be reconstructible from persisted events (`R8`). +* Deposits are only credited at *finalized* blocks, as today. + +### Address derivation and registration + +* Derivation path for account `(p, s)`: + `[SCHEMA_DEPOSIT_ADDRESS, p.as_slice(), s]` where `SCHEMA_DEPOSIT_ADDRESS = [1u8]` + is a schema tag reserving room for future schemes, and `s` is the 32-byte subaccount + (all-zero for the default subaccount). Non-empty by construction, hence distinct + from the main address path. +* The child *public key* (and hence the address) is computed locally from the cached + master public key using non-hardened derivation (`ic-secp256k1`'s + `derive_subkey` / `DerivationPath`, as ckBTC does) — no management-canister call and + no signature is needed to *create* an address; `sign_with_ecdsa` with the same path + is only invoked to sign authorizations (and, as a recovery fallback, transactions). +* New endpoint `get_deposit_address(account) -> String` (EIP-55 checksummed). The + first call is an update call that registers the address in state + (`deposit_addresses: Account ↔ Address` bimap + per-address bookkeeping: + `registered_at_block`, delegation status, credited/swept counters), emitting a + `DepositAddressRegistered` audit event. Subsequent calls are cheap lookups. + +### Deposit detection and minting (Phase 1, ckERC20) + +* New endpoint `notify_deposit(account, token?)` (update, fee-less, guarded against + concurrent calls per account like `update_balance` in ckBTC): + 1. Look up the registered deposit address; determine the block range + `(last_checked_block + 1) ..= latest_finalized_block`. + 2. `eth_getLogs` with `address = token contract(s)`, `topics = [Transfer, + any, deposit_address]` over that range (chunked by the existing 500-block spread + logic). The response is tiny: it concerns a single recipient. + 3. For each log: validate amount ≥ per-token minimum (`R4`), screen the `Transfer` + sender against the blocklist (`R3`), deduplicate by `(tx_hash, log_index)` and + record `AcceptedErc20Deposit`-style events with a new deposit-source variant; + invalid ones are recorded like today's `InvalidDeposit`. + 4. Mint through the existing `mint()` path with `amount - deposit_fee`, reusing the + ledger-client, memo (tx hash + log index) and quarantine-on-panic machinery + (`R2`). +* A frontend (e.g. OISY) polls `notify_deposit` after showing the address, so the flow + is automatic from the user's perspective; a background timer may additionally + re-check addresses with recent activity, bounded to a fixed batch per tick. + +### Sweeper delegate contract + +A single immutable Solidity contract, deployed once per network, with **no storage** +(EIP-7702 delegates share the EOA's storage; using none avoids collision hazards +entirely) and the minter's main address hardcoded as an `immutable`: + +```solidity +contract CkSweeper { + address payable private immutable MINTER; + constructor(address minter) { MINTER = payable(minter); } + + /// Callable by anyone: funds can only move to MINTER (R6). + function sweepErc20(IERC20[] calldata tokens) external { + for (uint i = 0; i < tokens.length; ++i) { + uint256 b = tokens[i].balanceOf(address(this)); + if (b > 0) tokens[i].safeTransfer(MINTER, b); // USDT-safe transfer + } + } + + function sweepEth() external { + if (address(this).balance > 0) { + (bool ok,) = MINTER.call{value: address(this).balance}(""); + require(ok); + } + } +} +``` + +Notes: `safeTransfer` handles non-standard ERC-20s (USDT returns no value); no +`receive()` is defined on purpose — plain ETH sends to a *delegated* address are meant +to fail rather than be silently accepted while delegated (Phase 2, `R12`); reentrancy +is moot (fixed destination, no state). + +### EIP-7702 support in the transaction layer (`src/tx.rs`) + +* New `Eip7702TransactionRequest` with `SET_CODE_TX_ID: u8 = 4`, payload + `0x04 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, + to, value, data, access_list, authorization_list, y_parity, r, s])`. +* `AuthorizationTuple { chain_id, delegate, nonce, y_parity, r, s }`, signed over + `keccak256(0x05 || rlp([chain_id, delegate, nonce]))` with `sign_with_ecdsa` using + the deposit address' derivation path; `chain_id` is set explicitly (never 0) to + prevent cross-chain replay; recovery-id determination reuses the existing + `Eip1559Signature` machinery. +* Deposit-EOA nonces: fetched via `eth_getTransactionCount` (finalized) with the usual + consensus strategy at authorization-signing time; an applied authorization increments + the EOA nonce, tracked in state to avoid re-fetching. Deposit EOAs never send + transactions themselves, so races are limited to re-delegation. +* Resubmission with fee bumping mirrors the existing `Resubmittable` logic. + +### Sweeping task + +* A periodic task selects deposit addresses with credited-but-unswept balances where + `unswept_value ≥ sweep_gas_cost × margin` or `age > max_age`, up to `N` addresses + per batch (gas-limit bound; initial `N ≈ 20`). +* One type-`0x04` transaction from the main address: + * `authorization_list`: tuples for all not-yet-delegated addresses in the batch + (≈ 12'500–25'000 gas each, one-time); + * `to = Multicall3`, `data = aggregate3([...])` calling + `sweepErc20([tokens])` on each deposit address (≈ 30–50k gas per address/token). +* Confirmation via transaction receipt, exactly like withdrawals; on success emit + `SweepConfirmed` events updating per-address `total_swept`. Sweep gas is paid from + the main address' ETH balance and recouped by `deposit_fee` (`R7`); the effective + fee/cost ratio is exported as a metric to recalibrate fees via proposal. +* Accounting invariant: main-address liquidity for withdrawals = today's + `eth_balance` bookkeeping; credited-but-unswept amounts are tracked per address and + shown on the dashboard (`R9`). + +### Phase 2: native ETH + +Two ETH-specific problems and their resolutions: + +1. **Detection without logs.** Plain ETH transfers emit no events, and CEXs may send + ETH via internal transactions (contract-batched withdrawals), which even + `eth_getTransactionByHash` cannot attribute without trace APIs. Detection is + therefore **balance-based**: `notify_deposit` reads `eth_getBalance(addr, + finalized)` and credits `balance + total_swept - total_credited` when the delta + exceeds the ETH minimum (`R11`). This is robust to internal transactions and + requires no trace support from providers. + * Compliance caveat: balance deltas carry no sender information. Screening is + limited to (a) optional sender screening when the caller supplies withdrawal + transaction hashes, and (b) address-level screening. This weakening relative to + `R3` is an accepted limitation (see Non-goals) and should be reviewed with + compliance before Phase 2 ships. +2. **Fixed 21'000-gas transfers vs delegated code (`R12`).** A value transfer to an + address with EIP-7702 delegated code *executes* that code, and a transfer with + exactly 21'000 gas has zero gas left for execution — it always fails. Exchanges + commonly hard-code 21'000 for ETH withdrawals. Phase 2 therefore switches the + delegation lifecycle for accounts using ETH deposits from *permanent* to + *set-and-clear*: + * Sweep batch transaction 1: authorizations installing the delegate + Multicall3 + `sweepEth()`/`sweepErc20()` calls; + * Sweep batch transaction 2: authorizations delegating to `address(0)`, which + clears the code (per EIP-7702), restoring a plain EOA. + * Cost: two tECDSA signatures and ≈ 2 × 12'500–25'000 gas per address per sweep + cycle, instead of one signature ever. Outside the short set→clear window, the + address is codeless and fixed-gas transfers succeed; inside it they fail on the + sender side without loss (`R12`). + * Whether Phase 1 addresses also move to set-and-clear (uniform policy) or keep + permanent delegation (cheaper) is decided at Phase 2 based on measured CEX + behavior for ETH withdrawals. + +### Test plan + +Unit tests (in `tests.rs` files per module, helpers in `test_fixtures.rs`): + +* Address derivation: determinism, uniqueness across principals/subaccounts, + non-collision with the main address, EIP-55 encoding (`R1`). +* Type-`0x04` transaction and authorization encoding/signing against EIP-7702 + published test vectors; authorization hash `0x05 || rlp(...)`; recovery-id + round-trip (`R5`, `R6` plumbing). +* `Transfer`-log parsing, minimum/fee arithmetic incl. `amount ≤ fee` rejection + (`R2`, `R4`), blocklist screening (`R3`), dedup by `(tx_hash, log_index)` (`R2`). +* Balance-delta crediting monotonicity across sweep interleavings (`R11`). +* Event replay: state reconstructed from audit events equals live state (`R8`). + +Integration tests (state-machine tests in `rs/ethereum/cketh/minter/tests` with the +mocked EVM-RPC canister, extending the existing fixtures): + +* End-to-end Phase 1 happy path: register address → mock `Transfer` log → notify → + mint − fee → sweep tx submitted with expected `0x04` payload → receipt → swept + (`R2`, `R5`, `R7`). +* Double-notify / concurrent-notify produce a single mint (`R2`); blocked sender + (`R3`); below-minimum and unsupported token (`R4`); sweep failure then retry with + fee bump, mint unaffected (`R5`); withdrawal flow regression (`R10`); dashboard + rendering (`R9`). +* Solidity: Foundry tests for `CkSweeper` (permissionless sweep only reaches minter, + USDT-style token, delegated-EOA execution against a Prague-enabled local node) + (`R6`, `R12`). + +Verification commands: `bazel test //rs/ethereum/cketh/minter:lib_unit_tests +//rs/ethereum/cketh/minter/tests:...` (exact targets per PR), plus `forge test` for +the delegate. + +### Delivery / PR sequence + +1. **EIP-7702 transaction support** in `src/tx.rs` + authorization signing in + `src/management` — pure library code, no behavior change. AC: encoding/signing unit + tests vs. EIP test vectors. +2. **Deposit address derivation, registration state, `get_deposit_address`** + audit + events + dashboard section. AC: `R1`, `R8`, `R9` (addresses only). +3. **ckERC20 deposit detection and minting** (`notify_deposit`, log queries, fees, + minimums, blocklist). AC: `R2`, `R3`, `R4`, `R7` (fee deduction), `R8`. +4. **Sweeper delegate contract** (Solidity, audited) + **sweeping task** (delegation, + Multicall3 batching, receipts, metrics). AC: `R5`, `R6`, `R7`, `R9`, `R10`. +5. **Phase 1 launch on Sepolia**, then mainnet via NNS upgrade proposal; frontend + (OISY) integration of `get_deposit_address` + `notify_deposit` polling. +6. **Phase 2: ckETH** (balance-delta crediting, set-and-clear delegation lifecycle, + compliance sign-off). AC: `R11`, `R12`. + +## Discussed Alternatives + +* **CREATE2 counterfactual forwarder contracts** (the classic exchange pattern): a + factory computes `CREATE2(factory, salt = hash(account), forwarder_init_code)` + addresses; sweeping deploys the forwarder, which pushes funds to the minter and + `selfdestruct`s in the same transaction (still permitted post-EIP-6780), leaving the + address codeless. Pros: decade of production use by exchanges, no dependency on + EIP-7702 or a new transaction type, native-ETH-safe by construction. Rejected as + the primary design because funds at deposit addresses would be controlled *by code + alone* — a factory/forwarder bug strands funds with no recovery — whereas tECDSA + EOAs keep key-based recovery independent of any contract; CREATE2 also costs more + gas per sweep (redeployment every cycle) and inherits residual `selfdestruct` + protocol risk. It remains the documented fallback if EIP-7702 adoption in the + transaction layer is reconsidered. +* **ERC-4337 smart accounts + paymaster**: counterfactual 4337 accounts as deposit + addresses with sponsored sweeps. Rejected: the minter is already its own transaction + submitter with multi-provider consensus, so EntryPoint/bundler/paymaster + infrastructure adds ≈100k+ gas per operation, an external-bundler dependency, and a + large audit surface for zero benefit over EIP-7702 here. +* **EIP-2612 permit / Permit2 sponsored helper deposits**: gasless `depositWithPermit` + relayed by the minter. Does not address CEX at all (a hot wallet signs no custom + message) and mainnet USDT lacks EIP-2612; noted as possible future work for + self-custody UX only. +* **Attribution hacks on the single minter address**: sender-address registration + (CEX hot wallets are shared/unpredictable), exact-amount matching (collisions, + fee-adjusted amounts, griefable by front-running), or per-exchange integration of + the helper contract (business-development dependency, not a protocol design). All + rejected as unsound. +* **Continuous scraping of `Transfer` logs for all deposit addresses** instead of + claim-based detection: `eth_getLogs` with a growing disjunction of thousands of + recipient topics scales linearly in cost with the user base, must be chunked across + providers' topic limits, and still misses native ETH. Rejected in favor of targeted, + claim-triggered queries; can be revisited as an optimization for hot addresses. +* **Pre-funding deposit EOAs with ETH for gas** (no EIP-7702): requires one extra + funding transaction per sweep (≈21k gas + transfer latency), doubles the transaction + count, leaves ETH dust stranded on every deposit address, and complicates fee + accounting. Kept only as the implicit *recovery* path that key-controlled addresses + always allow. From efd020f72f05058e71754929460868696a807762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 14:05:05 +0000 Subject: [PATCH 02/13] docs: runnable EIP-7702 sweep demo for the deposit-from-CEX design Foundry-based script (local anvil, Prague hardfork) demonstrating the core mechanism: an unfunded minter-derived deposit EOA receives a plain USDT-style ERC-20 transfer and is swept to the minter in a single type-0x04 transaction, with all gas paid by the minter. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 5 + .../docs/deposit_from_cex_demo/.gitignore | 2 + .../contracts/CkSweeper.sol | 41 ++++++ .../contracts/MockUSDT.sol | 26 ++++ .../cketh/docs/deposit_from_cex_demo/demo.sh | 121 ++++++++++++++++++ .../docs/deposit_from_cex_demo/foundry.toml | 5 + 6 files changed, 200 insertions(+) create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol create mode 100755 rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/foundry.toml diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index 8bdd7cb274dc..1463a64eec9b 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -328,6 +328,11 @@ Two ETH-specific problems and their resolutions: ### Test plan +A runnable end-to-end demonstration of the sweep mechanism (unfunded deposit EOA, +plain USDT-style transfer, single type-`0x04` sweep transaction with gas paid by the +minter) against a local Prague-enabled node is available in +[`deposit_from_cex_demo/demo.sh`](deposit_from_cex_demo/demo.sh). + Unit tests (in `tests.rs` files per module, helpers in `test_fixtures.rs`): * Address derivation: determinism, uniqueness across principals/subaccounts, diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore b/rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore new file mode 100644 index 000000000000..d8a1d071d339 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore @@ -0,0 +1,2 @@ +cache/ +out/ diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol new file mode 100644 index 000000000000..32da8785ca71 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +interface IERC20View { + function balanceOf(address account) external view returns (uint256); +} + +/// EIP-7702 delegate for minter-controlled deposit addresses. +/// Stateless on purpose: a 7702 delegate executes in the EOA's storage context. +/// Sweep functions are callable by anyone since funds can only move to MINTER. +contract CkSweeper { + address payable private immutable MINTER; + + constructor(address minter) { + MINTER = payable(minter); + } + + function sweepErc20(address[] calldata tokens) external { + for (uint256 i = 0; i < tokens.length; ++i) { + uint256 balance = IERC20View(tokens[i]).balanceOf(address(this)); + if (balance > 0) { + _safeTransfer(tokens[i], balance); + } + } + } + + function sweepEth() external { + uint256 balance = address(this).balance; + if (balance > 0) { + (bool ok,) = MINTER.call{value: balance}(""); + require(ok, "ETH sweep failed"); + } + } + + /// Tolerates non-standard ERC-20s such as USDT whose transfer returns no value. + function _safeTransfer(address token, uint256 value) private { + (bool ok, bytes memory data) = + token.call(abi.encodeWithSignature("transfer(address,uint256)", MINTER, value)); + require(ok && (data.length == 0 || abi.decode(data, (bool))), "token sweep failed"); + } +} diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol new file mode 100644 index 000000000000..ee0026c8557d --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +/// Mimics USDT's non-standard ERC-20: `transfer` returns no value. +contract MockUSDT { + string public constant name = "Mock Tether USD"; + string public constant symbol = "USDT"; + uint8 public constant decimals = 6; + uint256 public immutable totalSupply; + mapping(address => uint256) public balanceOf; + + event Transfer(address indexed from, address indexed to, uint256 value); + + constructor(address initialHolder, uint256 initialSupply) { + balanceOf[initialHolder] = initialSupply; + totalSupply = initialSupply; + emit Transfer(address(0), initialHolder, initialSupply); + } + + function transfer(address to, uint256 value) external { + require(balanceOf[msg.sender] >= value, "insufficient balance"); + balanceOf[msg.sender] -= value; + balanceOf[to] += value; + emit Transfer(msg.sender, to, value); + } +} diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh b/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh new file mode 100755 index 000000000000..b57f779f708c --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# Demo of the "deposit from CEX" flow designed in ../deposit_from_cex.md, +# with the minter simulated by a plain EOA controlling a family of EOAs +# (stand-in for threshold ECDSA key derivation on the IC). +# +# 0) minter EOA + CEX hot-wallet EOA; CkSweeper delegate and a USDT-like +# ERC-20 are deployed. +# 1) the minter derives a user-specific deposit address. +# 2) that address is unfunded: 0 ETH, 0 USDT, no code. +# 3) the user withdraws USDT from the CEX: a plain ERC-20 transfer to the +# deposit address (the CEX pays that gas; the deposit address still has 0 ETH). +# 4) the minter sweeps the tokens in ONE type-0x04 (EIP-7702) transaction: +# authorization signed by the deposit EOA + call to sweepErc20, gas paid +# by the minter. The deposit address never holds any ETH. +# +# Requires foundry (anvil/forge/cast). If not installed locally, the script +# re-executes itself inside the official foundry Docker image. + +set -euo pipefail +cd "$(dirname "$0")" + +if ! command -v anvil >/dev/null 2>&1; then + if command -v docker >/dev/null 2>&1; then + echo "foundry not found locally; re-running inside ghcr.io/foundry-rs/foundry" + exec docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp/foundry-home \ + -v "$PWD":/demo -w /demo --entrypoint /demo/demo.sh \ + ghcr.io/foundry-rs/foundry:latest + fi + echo "error: neither foundry (anvil/forge/cast) nor docker is available" >&2 + exit 1 +fi + +RPC="http://127.0.0.1:8545" +CHAIN_ID=31337 + +# Anvil's first two well-known dev accounts. +MINTER_PK="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +CEX_PK="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" +MINTER=$(cast wallet address --private-key "$MINTER_PK") +CEX=$(cast wallet address --private-key "$CEX_PK") + +step() { printf '\n\033[1m== %s\033[0m\n' "$*"; } +show() { printf ' %-42s %s\n' "$1" "$2"; } +assert_eq() { + if [ "$1" != "$2" ]; then + echo "ASSERTION FAILED: expected '$2', got '$1' ($3)" >&2 + exit 1 + fi + echo " OK: $3" +} + +eth_balance() { cast balance "$1" --rpc-url "$RPC"; } +usdt_balance() { cast call "$USDT" "balanceOf(address)(uint256)" "$1" --rpc-url "$RPC" | awk '{print $1}'; } + +step "0) Setup: anvil (Prague), minter EOA, CEX hot wallet, contracts" +anvil --hardfork prague --silent & +ANVIL_PID=$! +trap 'kill "$ANVIL_PID" 2>/dev/null' EXIT +for _ in $(seq 1 50); do + cast block-number --rpc-url "$RPC" >/dev/null 2>&1 && break + sleep 0.2 +done + +SWEEPER=$(forge create contracts/CkSweeper.sol:CkSweeper --broadcast \ + --private-key "$MINTER_PK" --rpc-url "$RPC" --constructor-args "$MINTER" \ + | awk '/Deployed to:/ {print $3}') +USDT=$(forge create contracts/MockUSDT.sol:MockUSDT --broadcast \ + --private-key "$CEX_PK" --rpc-url "$RPC" --constructor-args "$CEX" 1000000000000 \ + | awk '/Deployed to:/ {print $3}') +show "minter (EOA, pays all sweep gas):" "$MINTER" +show "CEX hot wallet (EOA):" "$CEX" +show "CkSweeper delegate (sweeps only to minter):" "$SWEEPER" +show "MockUSDT (USDT-style ERC-20, 6 decimals):" "$USDT" + +step "1) Minter derives the user's deposit address" +# Demo stand-in for threshold ECDSA derivation: the "minter master key" +# deterministically derives one child key per IC account. On the IC the private +# key never exists anywhere; sign_with_ecdsa produces the signatures instead. +PRINCIPAL="k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae" +DEPOSIT_PK=$(cast keccak "cketh-deposit-address|${MINTER_PK}|${PRINCIPAL}") +DEPOSIT=$(cast wallet address --private-key "$DEPOSIT_PK") +show "IC principal:" "$PRINCIPAL" +show "deposit address:" "$DEPOSIT" + +step "2) Deposit address is unfunded: no ETH, no token, no code" +assert_eq "$(eth_balance "$DEPOSIT")" "0" "deposit address has 0 ETH" +assert_eq "$(usdt_balance "$DEPOSIT")" "0" "deposit address has 0 USDT" +assert_eq "$(cast code "$DEPOSIT" --rpc-url "$RPC")" "0x" "deposit address has no code" + +step "3) User withdraws 250 USDT from the CEX to the deposit address (plain ERC-20 transfer)" +cast send "$USDT" "transfer(address,uint256)" "$DEPOSIT" 250000000 \ + --private-key "$CEX_PK" --rpc-url "$RPC" >/dev/null +assert_eq "$(usdt_balance "$DEPOSIT")" "250000000" "deposit address received 250 USDT" +assert_eq "$(eth_balance "$DEPOSIT")" "0" "deposit address still has 0 ETH (cannot pay gas itself)" + +step "4) Minter sweeps in ONE EIP-7702 transaction (gas paid by the minter)" +# The deposit EOA signs an authorization (nonce 0, one-time) delegating its +# code to CkSweeper; the minter submits a single type-0x04 transaction carrying +# that authorization and calling sweepErc20 on the deposit address. +MINTER_USDT_BEFORE=$(usdt_balance "$MINTER") +AUTH=$(cast wallet sign-auth "$SWEEPER" --private-key "$DEPOSIT_PK" --nonce 0 --chain "$CHAIN_ID") +TX=$(cast send "$DEPOSIT" "sweepErc20(address[])" "[$USDT]" \ + --private-key "$MINTER_PK" --auth "$AUTH" --rpc-url "$RPC" --json) +show "sweep tx:" "$(echo "$TX" | grep -o '"transactionHash":"[^"]*"' | head -1 | cut -d'"' -f4)" +show "sweep tx type:" "$(echo "$TX" | grep -o '"type":"[^"]*"' | head -1 | cut -d'"' -f4) (0x4 = EIP-7702 SetCode)" + +assert_eq "$(usdt_balance "$DEPOSIT")" "0" "deposit address swept" +assert_eq "$(usdt_balance "$MINTER")" "$((MINTER_USDT_BEFORE + 250000000))" "minter received 250 USDT" +assert_eq "$(eth_balance "$DEPOSIT")" "0" "deposit address needed 0 ETH throughout" +DELEGATION="0xef0100$(echo "${SWEEPER#0x}" | tr '[:upper:]' '[:lower:]')" +assert_eq "$(cast code "$DEPOSIT" --rpc-url "$RPC")" "$DELEGATION" "deposit address now delegates to CkSweeper" + +step "Bonus) Next deposit needs no new authorization: delegation persists" +cast send "$USDT" "transfer(address,uint256)" "$DEPOSIT" 100000000 \ + --private-key "$CEX_PK" --rpc-url "$RPC" >/dev/null +cast send "$DEPOSIT" "sweepErc20(address[])" "[$USDT]" \ + --private-key "$MINTER_PK" --rpc-url "$RPC" >/dev/null +assert_eq "$(usdt_balance "$MINTER")" "$((MINTER_USDT_BEFORE + 350000000))" "second sweep via plain call, no authorization" + +printf '\n\033[1mDemo completed successfully.\033[0m\n' diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/foundry.toml b/rs/ethereum/cketh/docs/deposit_from_cex_demo/foundry.toml new file mode 100644 index 000000000000..399aa456c414 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/foundry.toml @@ -0,0 +1,5 @@ +[profile.default] +src = "contracts" +out = "out" +cache_path = "cache" +evm_version = "prague" From 30b28c75e886756046412c684f31599b47b677d9 Mon Sep 17 00:00:00 2001 From: IDX GitHub Automation Date: Fri, 3 Jul 2026 14:09:10 +0000 Subject: [PATCH 03/13] Automatically fixing code for linting and formatting issues --- rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh b/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh index b57f779f708c..95b9442b0cf8 100755 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh @@ -40,8 +40,8 @@ CEX_PK="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" MINTER=$(cast wallet address --private-key "$MINTER_PK") CEX=$(cast wallet address --private-key "$CEX_PK") -step() { printf '\n\033[1m== %s\033[0m\n' "$*"; } -show() { printf ' %-42s %s\n' "$1" "$2"; } +step() { printf '\n\033[1m== %s\033[0m\n' "$*"; } +show() { printf ' %-42s %s\n' "$1" "$2"; } assert_eq() { if [ "$1" != "$2" ]; then echo "ASSERTION FAILED: expected '$2', got '$1' ($3)" >&2 @@ -50,7 +50,7 @@ assert_eq() { echo " OK: $3" } -eth_balance() { cast balance "$1" --rpc-url "$RPC"; } +eth_balance() { cast balance "$1" --rpc-url "$RPC"; } usdt_balance() { cast call "$USDT" "balanceOf(address)(uint256)" "$1" --rpc-url "$RPC" | awk '{print $1}'; } step "0) Setup: anvil (Prague), minter EOA, CEX hot wallet, contracts" From 138bf61874e83a6fea237db8d155525d4ff0a505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 14:25:25 +0000 Subject: [PATCH 04/13] docs: rewrite deposit-from-CEX demo in Rust with alloy Replaces the bash/cast demo with a standalone cargo binary that prints the full sweep transaction details and asserts gas usage, and adds a batched sweep: one type-0x04 transaction targeting several deposit EOAs via a batch entry point on the CkSweeper delegate (measured ~26k marginal gas per additional EOA vs ~67k for a standalone sweep). The design doc is updated accordingly (batching no longer needs Multicall3). Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 30 +- .../docs/deposit_from_cex_demo/.gitignore | 1 + .../docs/deposit_from_cex_demo/Cargo.lock | 4863 +++++++++++++++++ .../docs/deposit_from_cex_demo/Cargo.toml | 15 + .../docs/deposit_from_cex_demo/README.md | 48 + .../artifacts/CkSweeper.bin.hex | 1 + .../artifacts/MockUSDT.bin.hex | 1 + .../contracts/CkSweeper.sol | 8 + .../cketh/docs/deposit_from_cex_demo/demo.sh | 121 - .../docs/deposit_from_cex_demo/src/main.rs | 316 ++ 10 files changed, 5273 insertions(+), 131 deletions(-) create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeper.bin.hex create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex delete mode 100755 rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index 1463a64eec9b..a4a1db85dbc6 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -142,10 +142,11 @@ The design is delivered in two phases: one transaction. No deposit address ever needs an ETH balance for gas. * **Sweeps are permissionless-safe.** The delegate's sweep functions are callable by anyone because the destination is hardcoded (`R6`); a third party triggering a sweep - only donates gas. This removes access-control state from the delegate and lets the - minter batch calls through the canonical, already-deployed - [Multicall3](https://www.multicall3.com/) (`0xcA11bde05977b3631167028862bE2a173976CA11`) - instead of deploying and auditing a custom batcher. + only donates gas. This removes access-control state from the delegate and lets one + transaction sweep many deposit addresses through a batch entry point on the deployed + `CkSweeper` instance itself — the delegate doubles as the batcher, so no additional + contract is needed (the canonical, already-deployed + [Multicall3](https://www.multicall3.com/) would work as well). * **Mint on finalized deposit, sweep asynchronously.** The user is credited as soon as the deposit is finalized and detected; sweeping is pure treasury consolidation, batched to amortize gas, and never blocks or reverts a mint (`R5`). This decouples @@ -244,6 +245,14 @@ contract CkSweeper { } } + /// Batch entry point: the deployed CkSweeper instance doubles as the + /// batcher, sweeping many delegated deposit EOAs in a single transaction. + function sweepErc20Batch(address[] calldata depositAddresses, address[] calldata tokens) external { + for (uint i = 0; i < depositAddresses.length; ++i) { + CkSweeper(depositAddresses[i]).sweepErc20(tokens); + } + } + function sweepEth() external { if (address(this).balance > 0) { (bool ok,) = MINTER.call{value: address(this).balance}(""); @@ -282,8 +291,9 @@ is moot (fixed destination, no state). * One type-`0x04` transaction from the main address: * `authorization_list`: tuples for all not-yet-delegated addresses in the batch (≈ 12'500–25'000 gas each, one-time); - * `to = Multicall3`, `data = aggregate3([...])` calling - `sweepErc20([tokens])` on each deposit address (≈ 30–50k gas per address/token). + * `to = CkSweeper`, `data = sweepErc20Batch(deposit_addresses, [tokens])`. Measured + in the runnable demo (mock token): ≈ 67k gas for a first single-address sweep + including its authorization, ≈ 26k marginal gas per additional address in a batch. * Confirmation via transaction receipt, exactly like withdrawals; on success emit `SweepConfirmed` events updating per-address `total_swept`. Sweep gas is paid from the main address' ETH balance and recouped by `deposit_fee` (`R7`); the effective @@ -328,10 +338,10 @@ Two ETH-specific problems and their resolutions: ### Test plan -A runnable end-to-end demonstration of the sweep mechanism (unfunded deposit EOA, -plain USDT-style transfer, single type-`0x04` sweep transaction with gas paid by the -minter) against a local Prague-enabled node is available in -[`deposit_from_cex_demo/demo.sh`](deposit_from_cex_demo/demo.sh). +A runnable end-to-end demonstration of the sweep mechanism (unfunded deposit EOAs, +plain USDT-style transfers, single and batched type-`0x04` sweep transactions with gas +paid by the minter, gas assertions) against a local Prague-enabled node is available +in [`deposit_from_cex_demo/`](deposit_from_cex_demo/README.md). Unit tests (in `tests.rs` files per module, helpers in `test_fixtures.rs`): diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore b/rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore index d8a1d071d339..00025442dd2a 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore @@ -1,2 +1,3 @@ cache/ out/ +target/ diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock b/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock new file mode 100644 index 000000000000..cce8fe4fd4c6 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock @@ -0,0 +1,4863 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ab0cd8afe573d1f7dc2353698a51b1f93aec362c8211e28cfd3948c6adba39" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "alloy-trie", +] + +[[package]] +name = "alloy-chains" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" +dependencies = [ + "alloy-primitives", + "num_enum", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f16daaf7e1f95f62c6c3bf8a3fc3d78b08ae9777810c0bb5e94966c7cd57ef0" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "rand 0.8.6", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "118998d9015332ab1b4720ae1f1e3009491966a0349938a1f43ff45a8a4c6299" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac9e0c34dc6bce643b182049cdfcca1b8ce7d9c260cbdd561f511873b7e26cd" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-core" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ddde5968de6044d67af107ad835bc0069a7ca245870b94c5958a7d8712b184" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a475bb02d9cef2dbb99065c1664ab3fe1f9352e21d6d5ed3f02cdbfc06ed1abc" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "itoa", + "serde", + "serde_json", + "winnow", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "k256", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "once_cell", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eips" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "serde", + "serde_with", + "sha2", +] + +[[package]] +name = "alloy-genesis" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf9480307b09d22876efb67d30cadd9013134c21f3a17ec9f93fd7536d38024" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-json-abi" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c36c9d7f9021601b04bfef14a4b64849f6d73116a4e91e071d7fbfe10247901" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422d110f1c40f1f8d0e5562b0b649c35f345fccb7093d9f02729943dcd1eef71" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "http", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7197a66d94c4de1591cdc16a9bcea5f8cccd0da81b865b49aef97b1b4016e0fa" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "derive_more", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb82711d59a43fdfd79727c99f270b974c784ec4eb5728a0d0d22f26716c87ef" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash", + "hashbrown 0.17.1", + "indexmap 2.14.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.4", + "rapidhash", + "ruint", + "rustc-hash", + "secp256k1 0.31.1", + "serde", + "sha3", +] + +[[package]] +name = "alloy-provider" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6b18b929ef1d078b834c3631e9c925177f3b23ddc6fa08a722d13047205876" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-anvil", + "alloy-rpc-types-debug", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-signer", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "either", + "futures", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-pubsub" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad54073131e7292d4e03e1aa2287730f737280eb160d8b579fb31939f558c11" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "auto_impl", + "bimap", + "futures", + "parking_lot", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "wasmtimer", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "alloy-rpc-client" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fcc9604042ca80bd37aa5e232ea1cd851f337e31e2babbbb345bc0b1c30de3" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4faad925d3a669ffc15f43b3deec7fbdf2adeb28a4d6f9cf4bc661698c0f8f4b" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-anvil", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-anvil" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47df51bedb3e6062cb9981187a51e86d0d64a4de66eb0855e9efe6574b044ddf" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3823026d1ed239a40f12364fac50726c8daf1b6ab8077a97212c5123910429ed" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-debug" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2145138f3214928f08cd13da3cb51ef7482b5920d8ac5a02ecd4e38d1a8f6d1e" +dependencies = [ + "alloy-primitives", + "derive_more", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb9b97b6e7965679ad22df297dda809b11cebc13405c1b537e5cffecc95834fa" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more", + "rand 0.8.6", + "serde", + "strum", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c095f92c4e1ff4981d89e9aa02d5f98c762a1980ab66bec49c44be11349da2" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-rpc-types-trace" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a4d010f86cd4e01e5205ec273911e538e1738e76d8bafe9ecd245910ea5a3" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-rpc-types-txpool" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942d26a2ca8891b26de4a8529d21091e21c1093e27eb99698f1a86405c76b1ff" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-serde" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f447aefab0f1c0649f71edc33f590992d4e122bc35fb9cdbbf67d4421ace85" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror", +] + +[[package]] +name = "alloy-signer-local" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f721f4bf2e4812e5505aaf5de16ef3065a8e26b9139ac885862d00b5a55a659a" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.6", + "thiserror", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840128ed2b2971d6d4668a553fe403a82683d3acc646c73e75887e7157408033" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63ec265e5d65d725175f6ca7711c970824c90ef9c0d1f1973711d4150ee612dd" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.14.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.118", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89bf01077f18650876cfa682eb1f949967b5cde03f1a51c955c469d2c9b4aa67" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.118", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857b470ecdd2ed38beaf82ad1a38c516a8ff75266750f38b9eeed001d575241b" +dependencies = [ + "serde", + "winnow", +] + +[[package]] +name = "alloy-sol-types" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384cf252de0db2dec52821eac037a7f57e2aa33fe5b900ce6fe39973402341f1" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8098f965442a9feb620965ba4b4be5e2b320f4ec5a3fff6bfa9e1ff7ef42bed1" +dependencies = [ + "alloy-json-rpc", + "auto_impl", + "base64", + "derive_more", + "futures", + "futures-utils-wasm", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8597d36d546e1dab822345ad563243ec3920e199322cb554ce56c8ef1a1e2e7" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "itertools 0.14.0", + "reqwest", + "serde_json", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-ipc" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1bd98c3870b8a44b79091dde5216a81d58ffbc1fd8ed61b776f9fee0f3bdf20" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3ab7a72b180992881acc112628b7668337a19ce15293ee974600ea7b693691" +dependencies = [ + "alloy-pubsub", + "alloy-transport", + "futures", + "http", + "rustls", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "url", + "ws_stream_wasm", +] + +[[package]] +name = "alloy-trie" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more", + "nybbles", + "serde", + "smallvec", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69722eddcdf1ce096c3ab66cf8116999363f734eb36fe94a148f4f71c85da84" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" +dependencies = [ + "ark-ff-asm 0.6.0", + "ark-ff-macros 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", + "educe", + "num-bigint", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "ark-ff-asm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "ark-ff-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.6.0", + "digest 0.10.7", + "num-bigint", + "serde_with", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.1", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +dependencies = [ + "hex-conservative 0.3.2", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.2", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "borsh" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "deposit-from-cex-demo" +version = "0.1.0" +dependencies = [ + "alloy", + "anyhow", + "hex", + "tokio", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.118", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "doctest-file" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f808d588c10e464ea6f7d3eaed500049eff30aaac103460f61828c2d65b3eb" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.6", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", + "serde", + "serde_core", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "interprocess" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.118", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "keccak-asm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "macro-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "nybbles" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" +dependencies = [ + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.1", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a224897b65b7ce38bf7b0adb569e7d77e11460d0cc3577908b263d8b5a89aa" +dependencies = [ + "rustversion", +] + +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "ruint" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45caf26f647c19115bf9c453c70ffe4a4a3a6390dceebd942610584f99b8ddce" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "ark-ff 0.6.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.6", + "rand 0.9.4", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.28", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.6", + "secp256k1-sys 0.10.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.4", + "secp256k1-sys 0.11.0", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec005042c7d952febc1a3ef5b0f6674e9054aa836877a31c90b20e25b3d31744" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.4", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.118", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.8", +] + +[[package]] +name = "webpki-roots" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.1", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml b/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml new file mode 100644 index 000000000000..4b02572c1aad --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "deposit-from-cex-demo" +version = "0.1.0" +edition = "2021" +publish = false + +# Standalone on purpose: this is a runnable design-doc demo, not part of the IC +# build (not a member of the root Cargo workspace, no bazel target). +[workspace] + +[dependencies] +alloy = { version = "1", features = ["full"] } +anyhow = "1" +hex = "0.4" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md b/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md new file mode 100644 index 000000000000..c5541ad01c1e --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md @@ -0,0 +1,48 @@ +# Deposit-from-CEX demo + +Runnable demonstration of the sweep mechanism designed in +[`../deposit_from_cex.md`](../deposit_from_cex.md), using [alloy](https://alloy.rs) +against a local Prague-enabled node. The minter is simulated by a plain EOA +controlling a family of derived EOAs (stand-in for threshold ECDSA on the IC). + +It shows that an **unfunded** deposit EOA (0 ETH, no code) receiving a plain +USDT-style ERC-20 transfer can be swept to the minter in a **single EIP-7702 +transaction** whose gas is paid entirely by the minter — first for one deposit +address, then **batched** (one transaction sweeping three deposit EOAs, their +authorizations riding in the same transaction's authorization list). + +## Run + +Start a Prague-enabled anvil: + +```shell +anvil --hardfork prague +``` + +or, without foundry installed: + +```shell +docker run --rm -p 8545:8545 ghcr.io/foundry-rs/foundry:latest \ + "anvil --host 0.0.0.0 --hardfork prague" +``` + +then: + +```shell +cargo run +``` + +(`ETH_RPC_URL` overrides the default `http://127.0.0.1:8545`.) + +## Contracts + +`contracts/` holds the Solidity sources (`CkSweeper.sol`, the EIP-7702 delegate +and batcher; `MockUSDT.sol`, a USDT-style ERC-20 whose `transfer` returns no +value). The deployment bytecode embedded in the binary lives in `artifacts/`; +regenerate it after changing the contracts with: + +```shell +forge build +grep -o '"object":"0x[0-9a-f]*"' out/CkSweeper.sol/CkSweeper.json | head -1 | sed 's/"object":"//;s/"//' > artifacts/CkSweeper.bin.hex +grep -o '"object":"0x[0-9a-f]*"' out/MockUSDT.sol/MockUSDT.json | head -1 | sed 's/"object":"//;s/"//' > artifacts/MockUSDT.bin.hex +``` diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeper.bin.hex b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeper.bin.hex new file mode 100644 index 000000000000..958df895a343 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeper.bin.hex @@ -0,0 +1 @@ +0x60a060405234801561000f575f5ffd5b50604051610b58380380610b58833981810160405281019061003191906100c9565b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050506100f4565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100988261006f565b9050919050565b6100a88161008e565b81146100b2575f5ffd5b50565b5f815190506100c38161009f565b92915050565b5f602082840312156100de576100dd61006b565b5b5f6100eb848285016100b5565b91505092915050565b608051610a456101135f395f818161019101526103270152610a455ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c806302c8012c146100435780639ac844141461005f578063f5d1ea2414610069575b5f5ffd5b61005d600480360381019061005891906104fb565b610085565b005b610067610182565b005b610083600480360381019061007e9190610546565b61025b565b005b5f5f90505b8282905081101561017d575f8383838181106100a9576100a86105c4565b5b90506020020160208101906100be919061064b565b73ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b81526004016100f69190610685565b602060405180830381865afa158015610111573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061013591906106d1565b90505f81111561017157610170848484818110610155576101546105c4565b5b905060200201602081019061016a919061064b565b8261030c565b5b5080600101905061008a565b505050565b5f4790505f811115610258575f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16826040516101d390610729565b5f6040518083038185875af1925050503d805f811461020d576040519150601f19603f3d011682016040523d82523d5f602084013e610212565b606091505b5050905080610256576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161024d90610797565b60405180910390fd5b505b50565b5f5f90505b848490508110156103055784848281811061027e5761027d6105c4565b5b9050602002016020810190610293919061064b565b73ffffffffffffffffffffffffffffffffffffffff166302c8012c84846040518363ffffffff1660e01b81526004016102cd929190610871565b5f604051808303815f87803b1580156102e4575f5ffd5b505af11580156102f6573d5f5f3e3d5ffd5b50505050806001019050610260565b5050505050565b5f5f8373ffffffffffffffffffffffffffffffffffffffff167f0000000000000000000000000000000000000000000000000000000000000000846040516024016103589291906108c2565b6040516020818303038152906040527fa9059cbb000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516103e29190610931565b5f604051808303815f865af19150503d805f811461041b576040519150601f19603f3d011682016040523d82523d5f602084013e610420565b606091505b509150915081801561044d57505f8151148061044c57508080602001905181019061044b919061097c565b5b5b61048c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610483906109f1565b60405180910390fd5b50505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126104bb576104ba61049a565b5b8235905067ffffffffffffffff8111156104d8576104d761049e565b5b6020830191508360208202830111156104f4576104f36104a2565b5b9250929050565b5f5f6020838503121561051157610510610492565b5b5f83013567ffffffffffffffff81111561052e5761052d610496565b5b61053a858286016104a6565b92509250509250929050565b5f5f5f5f6040858703121561055e5761055d610492565b5b5f85013567ffffffffffffffff81111561057b5761057a610496565b5b610587878288016104a6565b9450945050602085013567ffffffffffffffff8111156105aa576105a9610496565b5b6105b6878288016104a6565b925092505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61061a826105f1565b9050919050565b61062a81610610565b8114610634575f5ffd5b50565b5f8135905061064581610621565b92915050565b5f602082840312156106605761065f610492565b5b5f61066d84828501610637565b91505092915050565b61067f81610610565b82525050565b5f6020820190506106985f830184610676565b92915050565b5f819050919050565b6106b08161069e565b81146106ba575f5ffd5b50565b5f815190506106cb816106a7565b92915050565b5f602082840312156106e6576106e5610492565b5b5f6106f3848285016106bd565b91505092915050565b5f81905092915050565b50565b5f6107145f836106fc565b915061071f82610706565b5f82019050919050565b5f61073382610709565b9150819050919050565b5f82825260208201905092915050565b7f455448207377656570206661696c6564000000000000000000000000000000005f82015250565b5f61078160108361073d565b915061078c8261074d565b602082019050919050565b5f6020820190508181035f8301526107ae81610775565b9050919050565b5f82825260208201905092915050565b5f819050919050565b6107d781610610565b82525050565b5f6107e883836107ce565b60208301905092915050565b5f6108026020840184610637565b905092915050565b5f602082019050919050565b5f61082183856107b5565b935061082c826107c5565b805f5b858110156108645761084182846107f4565b61084b88826107dd565b97506108568361080a565b92505060018101905061082f565b5085925050509392505050565b5f6020820190508181035f83015261088a818486610816565b90509392505050565b5f61089d826105f1565b9050919050565b6108ad81610893565b82525050565b6108bc8161069e565b82525050565b5f6040820190506108d55f8301856108a4565b6108e260208301846108b3565b9392505050565b5f81519050919050565b8281835e5f83830152505050565b5f61090b826108e9565b61091581856106fc565b93506109258185602086016108f3565b80840191505092915050565b5f61093c8284610901565b915081905092915050565b5f8115159050919050565b61095b81610947565b8114610965575f5ffd5b50565b5f8151905061097681610952565b92915050565b5f6020828403121561099157610990610492565b5b5f61099e84828501610968565b91505092915050565b7f746f6b656e207377656570206661696c656400000000000000000000000000005f82015250565b5f6109db60128361073d565b91506109e6826109a7565b602082019050919050565b5f6020820190508181035f830152610a08816109cf565b905091905056fea26469706673582212203a28b8c14da8f13f437e7f491eec771d15bcb5cc8d07181297088fb510f4460564736f6c63430008230033 diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex new file mode 100644 index 000000000000..8441f6c041b8 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex @@ -0,0 +1 @@ +0x60a060405234801561000f575f5ffd5b5060405161086f38038061086f83398181016040528101906100319190610177565b805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555080608081815250508173ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516100d791906101c4565b60405180910390a350506101dd565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610113826100ea565b9050919050565b61012381610109565b811461012d575f5ffd5b50565b5f8151905061013e8161011a565b92915050565b5f819050919050565b61015681610144565b8114610160575f5ffd5b50565b5f815190506101718161014d565b92915050565b5f5f6040838503121561018d5761018c6100e6565b5b5f61019a85828601610130565b92505060206101ab85828601610163565b9150509250929050565b6101be81610144565b82525050565b5f6020820190506101d75f8301846101b5565b92915050565b60805161067a6101f55f395f610163015261067a5ff3fe608060405234801561000f575f5ffd5b5060043610610060575f3560e01c806306fdde031461006457806318160ddd14610082578063313ce567146100a057806370a08231146100be57806395d89b41146100ee578063a9059cbb1461010c575b5f5ffd5b61006c610128565b60405161007991906103d3565b60405180910390f35b61008a610161565b604051610097919061040b565b60405180910390f35b6100a8610185565b6040516100b5919061043f565b60405180910390f35b6100d860048036038101906100d391906104b6565b61018a565b6040516100e5919061040b565b60405180910390f35b6100f661019e565b60405161010391906103d3565b60405180910390f35b6101266004803603810190610121919061050b565b6101d7565b005b6040518060400160405280600f81526020017f4d6f636b2054657468657220555344000000000000000000000000000000000081525081565b7f000000000000000000000000000000000000000000000000000000000000000081565b600681565b5f602052805f5260405f205f915090505481565b6040518060400160405280600481526020017f555344540000000000000000000000000000000000000000000000000000000081525081565b805f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20541015610256576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161024d90610593565b60405180910390fd5b805f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546102a191906105de565b92505081905550805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546102f39190610611565b925050819055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610357919061040b565b60405180910390a35050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6103a582610363565b6103af818561036d565b93506103bf81856020860161037d565b6103c88161038b565b840191505092915050565b5f6020820190508181035f8301526103eb818461039b565b905092915050565b5f819050919050565b610405816103f3565b82525050565b5f60208201905061041e5f8301846103fc565b92915050565b5f60ff82169050919050565b61043981610424565b82525050565b5f6020820190506104525f830184610430565b92915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6104858261045c565b9050919050565b6104958161047b565b811461049f575f5ffd5b50565b5f813590506104b08161048c565b92915050565b5f602082840312156104cb576104ca610458565b5b5f6104d8848285016104a2565b91505092915050565b6104ea816103f3565b81146104f4575f5ffd5b50565b5f81359050610505816104e1565b92915050565b5f5f6040838503121561052157610520610458565b5b5f61052e858286016104a2565b925050602061053f858286016104f7565b9150509250929050565b7f696e73756666696369656e742062616c616e63650000000000000000000000005f82015250565b5f61057d60148361036d565b915061058882610549565b602082019050919050565b5f6020820190508181035f8301526105aa81610571565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6105e8826103f3565b91506105f3836103f3565b925082820390508181111561060b5761060a6105b1565b5b92915050565b5f61061b826103f3565b9150610626836103f3565b925082820190508082111561063e5761063d6105b1565b5b9291505056fea26469706673582212205ab6964be15449ed2c0cf9fc3e98bd4a0f4c7bf895cbccdf9c4b0262ede5479b64736f6c63430008230033 diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol index 32da8785ca71..8f0c781985c1 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol @@ -24,6 +24,14 @@ contract CkSweeper { } } + /// Batch entry point: the deployed CkSweeper instance doubles as the + /// batcher, sweeping many delegated deposit EOAs in a single transaction. + function sweepErc20Batch(address[] calldata depositAddresses, address[] calldata tokens) external { + for (uint256 i = 0; i < depositAddresses.length; ++i) { + CkSweeper(depositAddresses[i]).sweepErc20(tokens); + } + } + function sweepEth() external { uint256 balance = address(this).balance; if (balance > 0) { diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh b/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh deleted file mode 100755 index 95b9442b0cf8..000000000000 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/demo.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env bash -# -# Demo of the "deposit from CEX" flow designed in ../deposit_from_cex.md, -# with the minter simulated by a plain EOA controlling a family of EOAs -# (stand-in for threshold ECDSA key derivation on the IC). -# -# 0) minter EOA + CEX hot-wallet EOA; CkSweeper delegate and a USDT-like -# ERC-20 are deployed. -# 1) the minter derives a user-specific deposit address. -# 2) that address is unfunded: 0 ETH, 0 USDT, no code. -# 3) the user withdraws USDT from the CEX: a plain ERC-20 transfer to the -# deposit address (the CEX pays that gas; the deposit address still has 0 ETH). -# 4) the minter sweeps the tokens in ONE type-0x04 (EIP-7702) transaction: -# authorization signed by the deposit EOA + call to sweepErc20, gas paid -# by the minter. The deposit address never holds any ETH. -# -# Requires foundry (anvil/forge/cast). If not installed locally, the script -# re-executes itself inside the official foundry Docker image. - -set -euo pipefail -cd "$(dirname "$0")" - -if ! command -v anvil >/dev/null 2>&1; then - if command -v docker >/dev/null 2>&1; then - echo "foundry not found locally; re-running inside ghcr.io/foundry-rs/foundry" - exec docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp/foundry-home \ - -v "$PWD":/demo -w /demo --entrypoint /demo/demo.sh \ - ghcr.io/foundry-rs/foundry:latest - fi - echo "error: neither foundry (anvil/forge/cast) nor docker is available" >&2 - exit 1 -fi - -RPC="http://127.0.0.1:8545" -CHAIN_ID=31337 - -# Anvil's first two well-known dev accounts. -MINTER_PK="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" -CEX_PK="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" -MINTER=$(cast wallet address --private-key "$MINTER_PK") -CEX=$(cast wallet address --private-key "$CEX_PK") - -step() { printf '\n\033[1m== %s\033[0m\n' "$*"; } -show() { printf ' %-42s %s\n' "$1" "$2"; } -assert_eq() { - if [ "$1" != "$2" ]; then - echo "ASSERTION FAILED: expected '$2', got '$1' ($3)" >&2 - exit 1 - fi - echo " OK: $3" -} - -eth_balance() { cast balance "$1" --rpc-url "$RPC"; } -usdt_balance() { cast call "$USDT" "balanceOf(address)(uint256)" "$1" --rpc-url "$RPC" | awk '{print $1}'; } - -step "0) Setup: anvil (Prague), minter EOA, CEX hot wallet, contracts" -anvil --hardfork prague --silent & -ANVIL_PID=$! -trap 'kill "$ANVIL_PID" 2>/dev/null' EXIT -for _ in $(seq 1 50); do - cast block-number --rpc-url "$RPC" >/dev/null 2>&1 && break - sleep 0.2 -done - -SWEEPER=$(forge create contracts/CkSweeper.sol:CkSweeper --broadcast \ - --private-key "$MINTER_PK" --rpc-url "$RPC" --constructor-args "$MINTER" \ - | awk '/Deployed to:/ {print $3}') -USDT=$(forge create contracts/MockUSDT.sol:MockUSDT --broadcast \ - --private-key "$CEX_PK" --rpc-url "$RPC" --constructor-args "$CEX" 1000000000000 \ - | awk '/Deployed to:/ {print $3}') -show "minter (EOA, pays all sweep gas):" "$MINTER" -show "CEX hot wallet (EOA):" "$CEX" -show "CkSweeper delegate (sweeps only to minter):" "$SWEEPER" -show "MockUSDT (USDT-style ERC-20, 6 decimals):" "$USDT" - -step "1) Minter derives the user's deposit address" -# Demo stand-in for threshold ECDSA derivation: the "minter master key" -# deterministically derives one child key per IC account. On the IC the private -# key never exists anywhere; sign_with_ecdsa produces the signatures instead. -PRINCIPAL="k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae" -DEPOSIT_PK=$(cast keccak "cketh-deposit-address|${MINTER_PK}|${PRINCIPAL}") -DEPOSIT=$(cast wallet address --private-key "$DEPOSIT_PK") -show "IC principal:" "$PRINCIPAL" -show "deposit address:" "$DEPOSIT" - -step "2) Deposit address is unfunded: no ETH, no token, no code" -assert_eq "$(eth_balance "$DEPOSIT")" "0" "deposit address has 0 ETH" -assert_eq "$(usdt_balance "$DEPOSIT")" "0" "deposit address has 0 USDT" -assert_eq "$(cast code "$DEPOSIT" --rpc-url "$RPC")" "0x" "deposit address has no code" - -step "3) User withdraws 250 USDT from the CEX to the deposit address (plain ERC-20 transfer)" -cast send "$USDT" "transfer(address,uint256)" "$DEPOSIT" 250000000 \ - --private-key "$CEX_PK" --rpc-url "$RPC" >/dev/null -assert_eq "$(usdt_balance "$DEPOSIT")" "250000000" "deposit address received 250 USDT" -assert_eq "$(eth_balance "$DEPOSIT")" "0" "deposit address still has 0 ETH (cannot pay gas itself)" - -step "4) Minter sweeps in ONE EIP-7702 transaction (gas paid by the minter)" -# The deposit EOA signs an authorization (nonce 0, one-time) delegating its -# code to CkSweeper; the minter submits a single type-0x04 transaction carrying -# that authorization and calling sweepErc20 on the deposit address. -MINTER_USDT_BEFORE=$(usdt_balance "$MINTER") -AUTH=$(cast wallet sign-auth "$SWEEPER" --private-key "$DEPOSIT_PK" --nonce 0 --chain "$CHAIN_ID") -TX=$(cast send "$DEPOSIT" "sweepErc20(address[])" "[$USDT]" \ - --private-key "$MINTER_PK" --auth "$AUTH" --rpc-url "$RPC" --json) -show "sweep tx:" "$(echo "$TX" | grep -o '"transactionHash":"[^"]*"' | head -1 | cut -d'"' -f4)" -show "sweep tx type:" "$(echo "$TX" | grep -o '"type":"[^"]*"' | head -1 | cut -d'"' -f4) (0x4 = EIP-7702 SetCode)" - -assert_eq "$(usdt_balance "$DEPOSIT")" "0" "deposit address swept" -assert_eq "$(usdt_balance "$MINTER")" "$((MINTER_USDT_BEFORE + 250000000))" "minter received 250 USDT" -assert_eq "$(eth_balance "$DEPOSIT")" "0" "deposit address needed 0 ETH throughout" -DELEGATION="0xef0100$(echo "${SWEEPER#0x}" | tr '[:upper:]' '[:lower:]')" -assert_eq "$(cast code "$DEPOSIT" --rpc-url "$RPC")" "$DELEGATION" "deposit address now delegates to CkSweeper" - -step "Bonus) Next deposit needs no new authorization: delegation persists" -cast send "$USDT" "transfer(address,uint256)" "$DEPOSIT" 100000000 \ - --private-key "$CEX_PK" --rpc-url "$RPC" >/dev/null -cast send "$DEPOSIT" "sweepErc20(address[])" "[$USDT]" \ - --private-key "$MINTER_PK" --rpc-url "$RPC" >/dev/null -assert_eq "$(usdt_balance "$MINTER")" "$((MINTER_USDT_BEFORE + 350000000))" "second sweep via plain call, no authorization" - -printf '\n\033[1mDemo completed successfully.\033[0m\n' diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs b/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs new file mode 100644 index 000000000000..2cc5d34a8312 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs @@ -0,0 +1,316 @@ +//! Demo of the "deposit from CEX" flow designed in ../deposit_from_cex.md, +//! with the minter simulated by a plain EOA controlling a family of EOAs +//! (stand-in for threshold ECDSA key derivation on the IC). +//! +//! 0) minter EOA + CEX hot-wallet EOA; CkSweeper delegate and a USDT-like +//! ERC-20 are deployed. +//! 1) the minter derives user-specific deposit addresses. +//! 2) those addresses are unfunded: 0 ETH, 0 USDT, no code. +//! 3) users withdraw USDT from the CEX: plain ERC-20 transfers to the +//! deposit addresses (the CEX pays that gas). +//! 4) the minter sweeps a deposit address in ONE type-0x04 (EIP-7702) +//! transaction: authorization signed by the deposit EOA + call to +//! sweepErc20, gas paid by the minter. +//! 5) batched sweep: ONE transaction targets several deposit EOAs at once +//! via CkSweeper.sweepErc20Batch, their authorizations riding in the same +//! transaction's authorization list. +//! +//! Requires a Prague-enabled dev node, e.g. +//! anvil --hardfork prague +//! or, without foundry installed: +//! docker run --rm -p 8545:8545 ghcr.io/foundry-rs/foundry:latest \ +//! "anvil --host 0.0.0.0 --hardfork prague" + +use alloy::{ + eips::eip7702::{Authorization, SignedAuthorization}, + network::{EthereumWallet, TransactionBuilder, TransactionBuilder7702}, + primitives::{Address, U256, keccak256}, + providers::{DynProvider, Provider, ProviderBuilder}, + rpc::types::{TransactionReceipt, TransactionRequest}, + signers::{SignerSync, local::PrivateKeySigner}, + sol, + sol_types::{SolCall, SolConstructor, SolEvent}, +}; +use anyhow::{Context, Result, ensure}; + +sol! { + #[sol(rpc)] + contract CkSweeper { + constructor(address minter); + function sweepErc20(address[] calldata tokens) external; + function sweepErc20Batch(address[] calldata depositAddresses, address[] calldata tokens) external; + } + + #[sol(rpc)] + contract MockUSDT { + constructor(address initialHolder, uint256 initialSupply); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 value) external; + event Transfer(address indexed from, address indexed to, uint256 value); + } +} + +const CKSWEEPER_BYTECODE: &str = include_str!("../artifacts/CkSweeper.bin.hex"); +const MOCKUSDT_BYTECODE: &str = include_str!("../artifacts/MockUSDT.bin.hex"); + +// Anvil's first two well-known dev accounts. +const MINTER_PK: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; +const CEX_PK: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; + +const USDT_SUPPLY: u64 = 1_000_000_000_000; // 1M USDT (6 decimals) + +fn step(title: &str) { + println!("\n== {title}"); +} + +fn show(label: &str, value: impl std::fmt::Display) { + println!(" {label:<46} {value}"); +} + +fn ok(what: &str) { + println!(" OK: {what}"); +} + +/// Demo stand-in for threshold ECDSA derivation: the "minter master key" +/// deterministically derives one child key per IC account. On the IC the +/// private key never exists anywhere; sign_with_ecdsa produces the signatures. +fn derive_deposit_signer(principal: &str) -> PrivateKeySigner { + let seed = keccak256(format!("cketh-deposit-address|{MINTER_PK}|{principal}")); + PrivateKeySigner::from_bytes(&seed).expect("valid derived key") +} + +async fn deploy( + provider: &DynProvider, + from: Address, + bytecode_hex: &str, + constructor_args: Vec, +) -> Result
{ + let mut code = hex::decode(bytecode_hex.trim().trim_start_matches("0x"))?; + code.extend(constructor_args); + let tx = TransactionRequest::default() + .with_from(from) + .with_deploy_code(code); + let receipt = provider.send_transaction(tx).await?.get_receipt().await?; + receipt.contract_address.context("no contract address") +} + +fn sign_delegation( + deposit_signer: &PrivateKeySigner, + sweeper: Address, + chain_id: u64, +) -> Result { + let authorization = Authorization { + chain_id: U256::from(chain_id), + address: sweeper, + nonce: 0, + }; + let signature = deposit_signer.sign_hash_sync(&authorization.signature_hash())?; + Ok(authorization.into_signed(signature)) +} + +fn print_sweep_transaction(receipt: &TransactionReceipt, authorizations: &[SignedAuthorization]) { + show("transaction hash:", receipt.transaction_hash); + show( + "transaction type:", + format!( + "{} (4 = EIP-7702 SetCode)", + receipt.transaction_type() as u8 + ), + ); + show("from (pays gas):", receipt.from); + show("to:", receipt.to.expect("call, not create")); + show("gas used:", receipt.gas_used); + show( + "effective gas price:", + format!("{} wei", receipt.effective_gas_price), + ); + for auth in authorizations { + show( + "authorization:", + format!( + "EOA {} -> delegate {} (nonce {})", + auth.recover_authority().expect("valid signature"), + auth.address, + auth.nonce + ), + ); + } + for log in receipt.logs() { + if let Ok(transfer) = MockUSDT::Transfer::decode_log(&log.inner) { + show( + "Transfer event:", + format!( + "{} -> {}: {} USDT", + transfer.from, + transfer.to, + transfer.value / U256::from(1_000_000) + ), + ); + } + } +} + +#[tokio::main] +async fn main() -> Result<()> { + let rpc_url = + std::env::var("ETH_RPC_URL").unwrap_or_else(|_| "http://127.0.0.1:8545".to_string()); + + let minter_signer: PrivateKeySigner = MINTER_PK.parse()?; + let cex_signer: PrivateKeySigner = CEX_PK.parse()?; + let minter = minter_signer.address(); + let cex = cex_signer.address(); + let mut wallet = EthereumWallet::from(minter_signer); + wallet.register_signer(cex_signer); + let provider = ProviderBuilder::new() + .wallet(wallet) + .connect_http(rpc_url.parse()?) + .erased(); + let chain_id = provider.get_chain_id().await?; + + step("0) Setup: minter EOA, CEX hot wallet, contracts"); + let sweeper_address = deploy( + &provider, + minter, + CKSWEEPER_BYTECODE, + CkSweeper::constructorCall { minter }.abi_encode(), + ) + .await?; + let usdt_address = deploy( + &provider, + cex, + MOCKUSDT_BYTECODE, + MockUSDT::constructorCall { + initialHolder: cex, + initialSupply: U256::from(USDT_SUPPLY), + } + .abi_encode(), + ) + .await?; + let usdt = MockUSDT::new(usdt_address, provider.clone()); + show("minter (EOA, pays all sweep gas):", minter); + show("CEX hot wallet (EOA):", cex); + show("CkSweeper delegate + batcher:", sweeper_address); + show("MockUSDT (USDT-style ERC-20, 6 decimals):", usdt_address); + + step("1) Minter derives user-specific deposit addresses"); + let principals = [ + "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", + "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe", + "6b4pv-sbpcm-4nsnw-4iplt-t46wy-hbdla-vhemq-lvhlv-tghdk-k6gao-mae", + "5nl2b-p6c6f-h4o7v-erwra-ceapp-ai4fw-ze2cs-6xsyq-mnhwq-kkwoa-yqe", + ]; + let deposit_signers: Vec = principals + .iter() + .map(|p| derive_deposit_signer(p)) + .collect(); + let deposit_addresses: Vec
= deposit_signers.iter().map(|s| s.address()).collect(); + for (principal, address) in principals.iter().zip(&deposit_addresses) { + show(&format!("{}...:", &principal[..20]), address); + } + + step("2) Deposit addresses are unfunded: no ETH, no token, no code"); + for address in &deposit_addresses { + ensure!(provider.get_balance(*address).await?.is_zero()); + ensure!(usdt.balanceOf(*address).call().await?.is_zero()); + ensure!(provider.get_code_at(*address).await?.is_empty()); + } + ok("every deposit address has 0 ETH, 0 USDT and no code"); + + step("3) Users withdraw USDT from the CEX (plain ERC-20 transfers)"); + let amounts: [u64; 4] = [250_000_000, 100_000_000, 75_000_000, 50_000_000]; + for (address, amount) in deposit_addresses.iter().zip(amounts) { + usdt.transfer(*address, U256::from(amount)) + .from(cex) + .send() + .await? + .get_receipt() + .await?; + show( + &format!("CEX -> {address}:"), + format!("{} USDT", amount / 1_000_000), + ); + } + for address in &deposit_addresses { + ensure!(provider.get_balance(*address).await?.is_zero()); + } + ok("deposit addresses still have 0 ETH (cannot pay gas themselves)"); + + step("4) Minter sweeps ONE deposit address in ONE EIP-7702 transaction"); + let single_authorization = sign_delegation(&deposit_signers[0], sweeper_address, chain_id)?; + let single_sweep = TransactionRequest::default() + .with_from(minter) + .with_to(deposit_addresses[0]) + .with_input( + CkSweeper::sweepErc20Call { + tokens: vec![usdt_address], + } + .abi_encode(), + ) + .with_authorization_list(vec![single_authorization.clone()]); + let single_receipt = provider + .send_transaction(single_sweep) + .await? + .get_receipt() + .await?; + print_sweep_transaction(&single_receipt, &[single_authorization]); + + ensure!(single_receipt.status(), "single sweep reverted"); + ensure!(usdt.balanceOf(deposit_addresses[0]).call().await?.is_zero()); + ensure!(usdt.balanceOf(minter).call().await? == U256::from(amounts[0])); + ok("250 USDT swept to the minter; deposit address needed 0 ETH throughout"); + let single_gas = single_receipt.gas_used; + ensure!( + (60_000..=90_000).contains(&single_gas), + "unexpected single-sweep gas: {single_gas}" + ); + ok(&format!( + "gas used {single_gas} within expected bounds [60000, 90000] \ + (21000 base + 25000 authorization + ~21000 delegated ERC-20 sweep)" + )); + + step("5) Batched sweep: ONE transaction targets the 3 remaining deposit EOAs"); + let batch_authorizations: Vec = deposit_signers[1..] + .iter() + .map(|signer| sign_delegation(signer, sweeper_address, chain_id)) + .collect::>()?; + let batch_targets = deposit_addresses[1..].to_vec(); + let batch_sweep = TransactionRequest::default() + .with_from(minter) + .with_to(sweeper_address) + .with_input( + CkSweeper::sweepErc20BatchCall { + depositAddresses: batch_targets.clone(), + tokens: vec![usdt_address], + } + .abi_encode(), + ) + .with_authorization_list(batch_authorizations.clone()); + let batch_receipt = provider + .send_transaction(batch_sweep) + .await? + .get_receipt() + .await?; + print_sweep_transaction(&batch_receipt, &batch_authorizations); + + ensure!(batch_receipt.status(), "batched sweep reverted"); + for address in &batch_targets { + ensure!(usdt.balanceOf(*address).call().await?.is_zero()); + } + let expected_total: u64 = amounts.iter().sum(); + ensure!(usdt.balanceOf(minter).call().await? == U256::from(expected_total)); + ok("all deposit addresses swept; minter now holds all 475 USDT"); + + let batch_gas = batch_receipt.gas_used; + ensure!( + batch_gas < 3 * single_gas, + "batching brought no amortization: {batch_gas} vs 3 x {single_gas}" + ); + let marginal = (batch_gas - single_gas) / 2; + ok(&format!( + "gas used {batch_gas} for 3 EOAs < 3 x {single_gas} (3 separate sweeps); \ + ~{marginal} gas per additional EOA" + )); + + println!("\nDemo completed successfully."); + Ok(()) +} From d828595abde324a201eec74cca78967deef6bc0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 14:29:12 +0000 Subject: [PATCH 05/13] docs: drop anvil --hardfork flag, pin foundry image version instead Anvil's default hardfork is the latest supported one, which includes EIP-7702 in any release since Pectra; reproducibility comes from pinning the foundry image version, not from naming the fork. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 4 ++-- rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md | 9 +++++---- rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs | 9 +++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index a4a1db85dbc6..ce08c877427f 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -340,8 +340,8 @@ Two ETH-specific problems and their resolutions: A runnable end-to-end demonstration of the sweep mechanism (unfunded deposit EOAs, plain USDT-style transfers, single and batched type-`0x04` sweep transactions with gas -paid by the minter, gas assertions) against a local Prague-enabled node is available -in [`deposit_from_cex_demo/`](deposit_from_cex_demo/README.md). +paid by the minter, gas assertions) against a local dev node (any post-Pectra +version) is available in [`deposit_from_cex_demo/`](deposit_from_cex_demo/README.md). Unit tests (in `tests.rs` files per module, helpers in `test_fixtures.rs`): diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md b/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md index c5541ad01c1e..df9a69e50d93 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md @@ -13,17 +13,18 @@ authorizations riding in the same transaction's authorization list). ## Run -Start a Prague-enabled anvil: +Start anvil (its default hardfork is the latest supported one, which includes +EIP-7702 in any foundry release ≥ v1.0 / Pectra): ```shell -anvil --hardfork prague +anvil ``` or, without foundry installed: ```shell -docker run --rm -p 8545:8545 ghcr.io/foundry-rs/foundry:latest \ - "anvil --host 0.0.0.0 --hardfork prague" +docker run --rm -p 8545:8545 ghcr.io/foundry-rs/foundry:v1.7.1 \ + "anvil --host 0.0.0.0" ``` then: diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs b/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs index 2cc5d34a8312..6a7a67a20be1 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs @@ -15,11 +15,12 @@ //! via CkSweeper.sweepErc20Batch, their authorizations riding in the same //! transaction's authorization list. //! -//! Requires a Prague-enabled dev node, e.g. -//! anvil --hardfork prague +//! Requires a dev node supporting EIP-7702 (Ethereum mainnet since the Pectra +//! upgrade, May 2025), e.g. +//! anvil //! or, without foundry installed: -//! docker run --rm -p 8545:8545 ghcr.io/foundry-rs/foundry:latest \ -//! "anvil --host 0.0.0.0 --hardfork prague" +//! docker run --rm -p 8545:8545 ghcr.io/foundry-rs/foundry:v1.7.1 \ +//! "anvil --host 0.0.0.0" use alloy::{ eips::eip7702::{Authorization, SignedAuthorization}, From cc578797c6975588049666ce9664e34c9a3a85dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 15:05:10 +0000 Subject: [PATCH 06/13] docs: address review comments on deposit-from-CEX demo - separate wallets for the minter and the CEX (unrelated parties) - rename *_PK constants to *_PRIVATE_KEY - print the raw signed transaction hex of each sweep - print the minter's nonce for every transaction it signs - hard-code the expected gas used by both sweep transactions - fix the EthBalance field reference in the design doc Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 3 +- .../docs/deposit_from_cex_demo/src/main.rs | 166 +++++++++++++----- 2 files changed, 122 insertions(+), 47 deletions(-) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index ce08c877427f..6632c6a42c42 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -27,7 +27,8 @@ Consequently a user holding e.g. USDT or USDC on Coinbase/Binance cannot onramp ckUSDT/ckUSDC without first withdrawing to a self-custody wallet, funding it with ETH for gas, and interacting with the helper contract — a prohibitive UX. Funds sent directly to the minter address today are simply unaccounted, with no recovery path -(see the comment on `EthBalance::eth_balance` in `src/state.rs`). +(see the documentation of the `eth_balance` field of the `EthBalance` struct in +`src/state.rs`). The only attribution channel a CEX supports is the **destination address**. This design therefore gives each IC account a **unique, deterministic deposit address**, controlled diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs b/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs index 6a7a67a20be1..844a2de2f805 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs @@ -23,7 +23,8 @@ //! "anvil --host 0.0.0.0" use alloy::{ - eips::eip7702::{Authorization, SignedAuthorization}, + consensus::{Transaction, TxEnvelope}, + eips::{eip2718::Encodable2718, eip7702::Authorization, eip7702::SignedAuthorization}, network::{EthereumWallet, TransactionBuilder, TransactionBuilder7702}, primitives::{Address, U256, keccak256}, providers::{DynProvider, Provider, ProviderBuilder}, @@ -55,11 +56,17 @@ const CKSWEEPER_BYTECODE: &str = include_str!("../artifacts/CkSweeper.bin.hex"); const MOCKUSDT_BYTECODE: &str = include_str!("../artifacts/MockUSDT.bin.hex"); // Anvil's first two well-known dev accounts. -const MINTER_PK: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; -const CEX_PK: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; +const MINTER_PRIVATE_KEY: &str = + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; +const CEX_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; const USDT_SUPPLY: u64 = 1_000_000_000_000; // 1M USDT (6 decimals) +// The demo is fully deterministic (fixed keys, fixed contracts, fresh chain), +// so the gas used by both sweep transactions is a constant. +const SINGLE_SWEEP_GAS_USED: u64 = 66_889; +const BATCH_SWEEP_GAS_USED: u64 = 118_982; + fn step(title: &str) { println!("\n== {title}"); } @@ -76,22 +83,55 @@ fn ok(what: &str) { /// deterministically derives one child key per IC account. On the IC the /// private key never exists anywhere; sign_with_ecdsa produces the signatures. fn derive_deposit_signer(principal: &str) -> PrivateKeySigner { - let seed = keccak256(format!("cketh-deposit-address|{MINTER_PK}|{principal}")); + let seed = keccak256(format!( + "cketh-deposit-address|{MINTER_PRIVATE_KEY}|{principal}" + )); PrivateKeySigner::from_bytes(&seed).expect("valid derived key") } -async fn deploy( +/// Fills chain id, nonce, fees and gas, then signs with the given wallet, +/// without sending. +async fn fill_and_sign( provider: &DynProvider, + wallet: &EthereumWallet, from: Address, + tx: TransactionRequest, +) -> Result { + let fees = provider.estimate_eip1559_fees().await?; + let mut tx = tx + .with_from(from) + .with_chain_id(provider.get_chain_id().await?) + .with_nonce(provider.get_transaction_count(from).await?) + .with_max_fee_per_gas(fees.max_fee_per_gas) + .with_max_priority_fee_per_gas(fees.max_priority_fee_per_gas); + let gas_limit = provider.estimate_gas(tx.clone()).await?; + tx = tx.with_gas_limit(gas_limit); + Ok(tx.build(wallet).await?) +} + +async fn deploy( + provider: &DynProvider, + wallet: &EthereumWallet, + deployer: &str, + deployer_address: Address, bytecode_hex: &str, constructor_args: Vec, ) -> Result
{ let mut code = hex::decode(bytecode_hex.trim().trim_start_matches("0x"))?; code.extend(constructor_args); - let tx = TransactionRequest::default() - .with_from(from) - .with_deploy_code(code); - let receipt = provider.send_transaction(tx).await?.get_receipt().await?; + let envelope = fill_and_sign( + provider, + wallet, + deployer_address, + TransactionRequest::default().with_deploy_code(code), + ) + .await?; + show(&format!("{deployer} nonce (deploy):"), envelope.nonce()); + let receipt = provider + .send_tx_envelope(envelope) + .await? + .get_receipt() + .await?; receipt.contract_address.context("no contract address") } @@ -109,7 +149,28 @@ fn sign_delegation( Ok(authorization.into_signed(signature)) } -fn print_sweep_transaction(receipt: &TransactionReceipt, authorizations: &[SignedAuthorization]) { +/// Signs a sweep transaction with the minter's wallet, prints it in full +/// (including the minter's nonce and the raw signed transaction hex), sends it +/// and prints the resulting receipt. +async fn send_and_print_sweep_transaction( + minter_provider: &DynProvider, + minter_wallet: &EthereumWallet, + minter: Address, + tx: TransactionRequest, + authorizations: &[SignedAuthorization], +) -> Result { + let envelope = fill_and_sign(minter_provider, minter_wallet, minter, tx).await?; + show("minter nonce (sweep):", envelope.nonce()); + show( + "raw signed transaction:", + alloy::hex::encode_prefixed(envelope.encoded_2718()), + ); + let receipt = minter_provider + .send_tx_envelope(envelope) + .await? + .get_receipt() + .await?; + show("transaction hash:", receipt.transaction_hash); show( "transaction type:", @@ -149,6 +210,7 @@ fn print_sweep_transaction(receipt: &TransactionReceipt, authorizations: &[Signe ); } } + Ok(receipt) } #[tokio::main] @@ -156,28 +218,37 @@ async fn main() -> Result<()> { let rpc_url = std::env::var("ETH_RPC_URL").unwrap_or_else(|_| "http://127.0.0.1:8545".to_string()); - let minter_signer: PrivateKeySigner = MINTER_PK.parse()?; - let cex_signer: PrivateKeySigner = CEX_PK.parse()?; + let minter_signer: PrivateKeySigner = MINTER_PRIVATE_KEY.parse()?; + let cex_signer: PrivateKeySigner = CEX_PRIVATE_KEY.parse()?; let minter = minter_signer.address(); let cex = cex_signer.address(); - let mut wallet = EthereumWallet::from(minter_signer); - wallet.register_signer(cex_signer); - let provider = ProviderBuilder::new() - .wallet(wallet) + // The minter and the CEX are unrelated parties: two separate wallets. + let minter_wallet = EthereumWallet::from(minter_signer); + let cex_wallet = EthereumWallet::from(cex_signer); + let minter_provider = ProviderBuilder::new() + .wallet(minter_wallet.clone()) + .connect_http(rpc_url.parse()?) + .erased(); + let cex_provider = ProviderBuilder::new() + .wallet(cex_wallet.clone()) .connect_http(rpc_url.parse()?) .erased(); - let chain_id = provider.get_chain_id().await?; + let chain_id = minter_provider.get_chain_id().await?; step("0) Setup: minter EOA, CEX hot wallet, contracts"); let sweeper_address = deploy( - &provider, + &minter_provider, + &minter_wallet, + "minter", minter, CKSWEEPER_BYTECODE, CkSweeper::constructorCall { minter }.abi_encode(), ) .await?; let usdt_address = deploy( - &provider, + &cex_provider, + &cex_wallet, + "CEX", cex, MOCKUSDT_BYTECODE, MockUSDT::constructorCall { @@ -187,7 +258,8 @@ async fn main() -> Result<()> { .abi_encode(), ) .await?; - let usdt = MockUSDT::new(usdt_address, provider.clone()); + let usdt = MockUSDT::new(usdt_address, minter_provider.clone()); + let usdt_as_cex = MockUSDT::new(usdt_address, cex_provider.clone()); show("minter (EOA, pays all sweep gas):", minter); show("CEX hot wallet (EOA):", cex); show("CkSweeper delegate + batcher:", sweeper_address); @@ -211,17 +283,17 @@ async fn main() -> Result<()> { step("2) Deposit addresses are unfunded: no ETH, no token, no code"); for address in &deposit_addresses { - ensure!(provider.get_balance(*address).await?.is_zero()); + ensure!(minter_provider.get_balance(*address).await?.is_zero()); ensure!(usdt.balanceOf(*address).call().await?.is_zero()); - ensure!(provider.get_code_at(*address).await?.is_empty()); + ensure!(minter_provider.get_code_at(*address).await?.is_empty()); } ok("every deposit address has 0 ETH, 0 USDT and no code"); step("3) Users withdraw USDT from the CEX (plain ERC-20 transfers)"); let amounts: [u64; 4] = [250_000_000, 100_000_000, 75_000_000, 50_000_000]; for (address, amount) in deposit_addresses.iter().zip(amounts) { - usdt.transfer(*address, U256::from(amount)) - .from(cex) + usdt_as_cex + .transfer(*address, U256::from(amount)) .send() .await? .get_receipt() @@ -232,14 +304,13 @@ async fn main() -> Result<()> { ); } for address in &deposit_addresses { - ensure!(provider.get_balance(*address).await?.is_zero()); + ensure!(minter_provider.get_balance(*address).await?.is_zero()); } ok("deposit addresses still have 0 ETH (cannot pay gas themselves)"); step("4) Minter sweeps ONE deposit address in ONE EIP-7702 transaction"); let single_authorization = sign_delegation(&deposit_signers[0], sweeper_address, chain_id)?; let single_sweep = TransactionRequest::default() - .with_from(minter) .with_to(deposit_addresses[0]) .with_input( CkSweeper::sweepErc20Call { @@ -248,12 +319,14 @@ async fn main() -> Result<()> { .abi_encode(), ) .with_authorization_list(vec![single_authorization.clone()]); - let single_receipt = provider - .send_transaction(single_sweep) - .await? - .get_receipt() - .await?; - print_sweep_transaction(&single_receipt, &[single_authorization]); + let single_receipt = send_and_print_sweep_transaction( + &minter_provider, + &minter_wallet, + minter, + single_sweep, + &[single_authorization], + ) + .await?; ensure!(single_receipt.status(), "single sweep reverted"); ensure!(usdt.balanceOf(deposit_addresses[0]).call().await?.is_zero()); @@ -261,11 +334,11 @@ async fn main() -> Result<()> { ok("250 USDT swept to the minter; deposit address needed 0 ETH throughout"); let single_gas = single_receipt.gas_used; ensure!( - (60_000..=90_000).contains(&single_gas), - "unexpected single-sweep gas: {single_gas}" + single_gas == SINGLE_SWEEP_GAS_USED, + "unexpected single-sweep gas: {single_gas}, expected {SINGLE_SWEEP_GAS_USED}" ); ok(&format!( - "gas used {single_gas} within expected bounds [60000, 90000] \ + "gas used is exactly {SINGLE_SWEEP_GAS_USED} \ (21000 base + 25000 authorization + ~21000 delegated ERC-20 sweep)" )); @@ -276,7 +349,6 @@ async fn main() -> Result<()> { .collect::>()?; let batch_targets = deposit_addresses[1..].to_vec(); let batch_sweep = TransactionRequest::default() - .with_from(minter) .with_to(sweeper_address) .with_input( CkSweeper::sweepErc20BatchCall { @@ -286,12 +358,14 @@ async fn main() -> Result<()> { .abi_encode(), ) .with_authorization_list(batch_authorizations.clone()); - let batch_receipt = provider - .send_transaction(batch_sweep) - .await? - .get_receipt() - .await?; - print_sweep_transaction(&batch_receipt, &batch_authorizations); + let batch_receipt = send_and_print_sweep_transaction( + &minter_provider, + &minter_wallet, + minter, + batch_sweep, + &batch_authorizations, + ) + .await?; ensure!(batch_receipt.status(), "batched sweep reverted"); for address in &batch_targets { @@ -303,13 +377,13 @@ async fn main() -> Result<()> { let batch_gas = batch_receipt.gas_used; ensure!( - batch_gas < 3 * single_gas, - "batching brought no amortization: {batch_gas} vs 3 x {single_gas}" + batch_gas == BATCH_SWEEP_GAS_USED, + "unexpected batch-sweep gas: {batch_gas}, expected {BATCH_SWEEP_GAS_USED}" ); let marginal = (batch_gas - single_gas) / 2; ok(&format!( - "gas used {batch_gas} for 3 EOAs < 3 x {single_gas} (3 separate sweeps); \ - ~{marginal} gas per additional EOA" + "gas used is exactly {BATCH_SWEEP_GAS_USED} for 3 EOAs < 3 x {single_gas} \ + (3 separate sweeps); ~{marginal} gas per additional EOA" )); println!("\nDemo completed successfully."); From 30979f9bc586f0935aa028d70babb74c298ad15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 15:28:24 +0000 Subject: [PATCH 07/13] docs: add sweep-through-helper as variant B of the deposit-from-CEX design The sweeper delegate can call the existing helper contract (DepositHelperWithSubaccount.sol) instead of transferring directly: the sweep then emits the canonical ReceivedEthOrErc20 event, so the minter's existing crediting pipeline is reused unchanged and deposit detection is demoted to a sweep-scheduling hint. Since the principal becomes a sweep argument, sweeping is restricted to the minter. Sweeps can be scheduled on deposits observed at the latest block without waiting for finality: crediting only follows the finalized helper event, so a reorg merely wastes gas. The decision between direct sweep (A) and sweep-through- helper (B) is left open in the design doc. The demo exercises both variants against the real helper bytecode, asserts the emitted events carry the right principals, and shows a non-minter sweep attempt being rejected. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 87 ++++- .../docs/deposit_from_cex_demo/Cargo.lock | 250 +++++++++++-- .../docs/deposit_from_cex_demo/Cargo.toml | 1 + .../docs/deposit_from_cex_demo/README.md | 37 +- .../artifacts/CkDeposit.bin.hex | 1 + .../artifacts/CkSweeperViaHelper.bin.hex | 1 + .../artifacts/MockUSDT.bin.hex | 2 +- .../contracts/CkSweeperViaHelper.sol | 66 ++++ .../contracts/MockUSDT.sol | 20 +- .../docs/deposit_from_cex_demo/src/main.rs | 342 ++++++++++++++++-- 10 files changed, 737 insertions(+), 70 deletions(-) create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkDeposit.bin.hex create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeperViaHelper.bin.hex create mode 100644 rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeperViaHelper.sol diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index 6632c6a42c42..45862abdb46e 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -141,17 +141,19 @@ The design is delivered in two phases: everything to the minter's main address*. Sweep transactions are type-`0x04` transactions sent from the funded main address; many deposit addresses are swept in one transaction. No deposit address ever needs an ETH balance for gas. -* **Sweeps are permissionless-safe.** The delegate's sweep functions are callable by - anyone because the destination is hardcoded (`R6`); a third party triggering a sweep - only donates gas. This removes access-control state from the delegate and lets one - transaction sweep many deposit addresses through a batch entry point on the deployed - `CkSweeper` instance itself — the delegate doubles as the batcher, so no additional - contract is needed (the canonical, already-deployed - [Multicall3](https://www.multicall3.com/) would work as well). -* **Mint on finalized deposit, sweep asynchronously.** The user is credited as soon as - the deposit is finalized and detected; sweeping is pure treasury consolidation, - batched to amortize gas, and never blocks or reverts a mint (`R5`). This decouples - UX latency from gas-optimization policy. +* **Sweeps are permissionless-safe** (variant A). The delegate's sweep functions are + callable by anyone because the destination is hardcoded (`R6`); a third party + triggering a sweep only donates gas. This removes access-control state from the + delegate and lets one transaction sweep many deposit addresses through a batch entry + point on the deployed `CkSweeper` instance itself — the delegate doubles as the + batcher, so no additional contract is needed (the canonical, already-deployed + [Multicall3](https://www.multicall3.com/) would work as well). Variant B trades this + property away, see the open decision below. +* **Mint on finalized deposit, sweep asynchronously** (variant A). The user is + credited as soon as the deposit is finalized and detected; sweeping is pure treasury + consolidation, batched to amortize gas, and never blocks or reverts a mint (`R5`). + This decouples UX latency from gas-optimization policy. Variant B instead credits on + the sweep's own finalized event, see the open decision below. * **Claim-based detection instead of continuous scraping.** Deposits are detected when a `notify_deposit` endpoint is called for a specific account (by the user, or transparently by a frontend/timer polling recently active addresses). A targeted @@ -169,6 +171,60 @@ The design is delivered in two phases: proposal-configurable rather than oracle-priced, for simplicity and predictability (`R7`). +### Open decision: what the sweep does — variant A (direct) vs variant B (through the helper contract) + +Both variants share everything above (address derivation, EIP-7702 delegation, +batching, fees); they differ in the delegate's action and, consequently, in *where +crediting happens*. The decision between them is **not yet taken**. + +**Variant A — direct sweep (`CkSweeper`).** The delegate transfers the funds straight +to the minter's main address. Crediting requires a *new* detection-and-mint path in +the minter (`notify_deposit` → finalized `Transfer` log → mint), with its own event +types, deduplication and audit trail. Sweeps are permissionless-safe and mint timing +is independent of sweep scheduling. Measured in the demo (mock USDT): 66'854 gas for +a first single-address sweep including its authorization, ≈ 26k marginal gas per +additional address in a batch. + +**Variant B — sweep through the existing helper contract (`CkSweeperViaHelper`).** +The delegate approves and calls `depositErc20` on the already-deployed helper +(`DepositHelperWithSubaccount.sol`): the helper moves the funds to the minter *and +emits the canonical `ReceivedEthOrErc20` event* — with the deposit EOA as `owner` and +a caller-supplied IC principal/subaccount — at the single contract address the minter +already scrapes. **The minter's crediting pipeline (scrape → parse → dedup → mint) is +reused unchanged.** Consequences: + +* *Massively smaller minter change.* No second crediting path: deposit detection is + demoted from a correctness-critical component to a mere sweep-scheduling hint (it + can be sloppy, delayed, or even frontend-driven without any double-mint or + lost-credit risk). Correctness lives entirely in the existing, battle-tested + pipeline. +* *Sweeping must be restricted to the minter.* The principal is a sweep argument, so a + permissionless sweep would let anyone credit a deposit to their own principal. The + delegate stays stateless via two immutables: `MINTER`, and `SELF` (the deployed + instance's own address, captured at construction), accepting + `msg.sender ∈ {MINTER, SELF}` so the batch entry point still works. Belt-and-braces, + the minter additionally validates scraped events whose `owner` is a registered + deposit address against its own address↔account map and quarantines mismatches. +* *Minting follows the sweep, but latency need not suffer.* The minter can schedule + the sweep as soon as it observes the deposit at the `latest` block, **without + waiting for the deposit to be finalized**: crediting only ever follows the + *finalized* helper event emitted by the sweep itself, so a reorg that drops the + deposit merely wastes the sweep's gas (the delegate sweeps a zero balance — a + no-op) and costs some cycles; a reorged sweep transaction is handled by the + existing nonce-tracking and resubmission machinery. End-to-end latency is then + comparable to today's helper flow (deposit inclusion + sweep + finalization). +* *Screening point shifts.* The scraped event's `from`/`owner` is the deposit EOA, + not the CEX hot wallet, so blocklist screening of the actual sender (`R3`) must + happen when scheduling the sweep, based on the observed `Transfer` log. +* *Extra gas per sweep* for `approve` + `transferFrom` + event. Measured in the demo: + 82'207 gas single (+15'353 vs variant A), 164'746 for a batch re-delegating three + EOAs and sweeping two of them through the helper. +* *Native ETH works symmetrically* via the helper's `depositEth` (Phase 2). + +Both variants are exercised end-to-end in the runnable demo, including the +adversarial case for variant B (a non-minter caller attempting to sweep with their +own principal is rejected). + ## Implementation ### Constraints @@ -231,7 +287,10 @@ The design is delivered in two phases: A single immutable Solidity contract, deployed once per network, with **no storage** (EIP-7702 delegates share the EOA's storage; using none avoids collision hazards -entirely) and the minter's main address hardcoded as an `immutable`: +entirely) and the minter's main address hardcoded as an `immutable`. Shown below for +variant A; variant B's delegate (`CkSweeperViaHelper` in the demo) has the same shape +but calls the helper's `depositErc20` with caller-supplied principal/subaccount and +restricts callers to the minter (see the open decision above): ```solidity contract CkSweeper { @@ -343,6 +402,10 @@ A runnable end-to-end demonstration of the sweep mechanism (unfunded deposit EOA plain USDT-style transfers, single and batched type-`0x04` sweep transactions with gas paid by the minter, gas assertions) against a local dev node (any post-Pectra version) is available in [`deposit_from_cex_demo/`](deposit_from_cex_demo/README.md). +It exercises both sweep variants — including variant B against the *real* +`DepositHelperWithSubaccount.sol` bytecode, asserting the emitted +`ReceivedEthOrErc20` events carry the right principals, re-delegation of already +delegated deposit EOAs, and the rejected non-minter sweep attempt. Unit tests (in `tests.rs` files per module, helpers in `test_fixtures.rs`): diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock b/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock index cce8fe4fd4c6..d28b7cd3a11a 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock @@ -69,7 +69,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -106,7 +106,7 @@ dependencies = [ "futures", "futures-util", "serde_json", - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -149,7 +149,7 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -175,7 +175,7 @@ dependencies = [ "borsh", "k256", "serde", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -189,7 +189,7 @@ dependencies = [ "borsh", "once_cell", "serde", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -253,7 +253,7 @@ dependencies = [ "http", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -280,7 +280,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -363,7 +363,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -399,7 +399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" dependencies = [ "alloy-rlp-derive", - "arrayvec", + "arrayvec 0.7.8", "bytes", ] @@ -527,7 +527,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -541,7 +541,7 @@ dependencies = [ "alloy-serde", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -579,7 +579,7 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -595,7 +595,7 @@ dependencies = [ "async-trait", "k256", "rand 0.8.6", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -686,7 +686,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "tower", "tracing", @@ -761,7 +761,7 @@ dependencies = [ "nybbles", "serde", "smallvec", - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -792,6 +792,15 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[package]] +name = "ar_archive_writer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348" +dependencies = [ + "object", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -840,7 +849,7 @@ dependencies = [ "ark-ff-macros 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", - "arrayvec", + "arrayvec 0.7.8", "digest 0.10.7", "educe", "itertools 0.13.0", @@ -986,7 +995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ "ark-std 0.5.0", - "arrayvec", + "arrayvec 0.7.8", "digest 0.10.7", "num-bigint", ] @@ -1055,6 +1064,12 @@ dependencies = [ "rand 0.8.6", ] +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.8" @@ -1175,6 +1190,29 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +[[package]] +name = "binread" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" +dependencies = [ + "binread_derive", + "lazy_static", + "rustversion", +] + +[[package]] +name = "binread_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -1350,6 +1388,41 @@ dependencies = [ "serde", ] +[[package]] +name = "candid" +version = "0.10.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1347fd6b24f5ad23e4ab16d49551a03d396410dcb086e8f969c21efdf4b1dbd8" +dependencies = [ + "anyhow", + "binread", + "byteorder", + "candid_derive", + "hex", + "ic_principal", + "leb128", + "num-bigint", + "num-traits", + "paste", + "pretty", + "serde", + "serde_bytes", + "stacker", + "thiserror 1.0.69", +] + +[[package]] +name = "candid_derive" +version = "0.10.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad381629d2d2940899c3a784ccd4fe59e255b5714e7520f016f839f07d2236e7" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "cc" version = "1.2.65" @@ -1502,6 +1575,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1606,6 +1688,7 @@ version = "0.1.0" dependencies = [ "alloy", "anyhow", + "candid", "hex", "tokio", ] @@ -1827,7 +1910,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" dependencies = [ - "arrayvec", + "arrayvec 0.7.8", "auto_impl", "bytes", ] @@ -1838,7 +1921,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" dependencies = [ - "arrayvec", + "arrayvec 0.7.8", "auto_impl", "bytes", ] @@ -2122,7 +2205,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ - "arrayvec", + "arrayvec 0.7.8", ] [[package]] @@ -2131,7 +2214,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360" dependencies = [ - "arrayvec", + "arrayvec 0.7.8", ] [[package]] @@ -2273,6 +2356,19 @@ dependencies = [ "cc", ] +[[package]] +name = "ic_principal" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8747dcb4d382080cc7c40ae9d80b33e35792ee06cb816b5c133dd6086d2f8730" +dependencies = [ + "crc32fast", + "data-encoding", + "serde", + "sha2", + "thiserror 1.0.69", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -2491,7 +2587,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror", + "thiserror 2.0.18", "walkdir", "windows-link", ] @@ -2598,6 +2694,18 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" + [[package]] name = "libc" version = "0.2.186" @@ -2688,6 +2796,7 @@ checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" dependencies = [ "num-integer", "num-traits", + "serde", ] [[package]] @@ -2760,6 +2869,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2778,7 +2896,7 @@ version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ - "arrayvec", + "arrayvec 0.7.8", "bitvec", "byte-slice-cast", "const_format", @@ -2927,6 +3045,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" +dependencies = [ + "arrayvec 0.5.2", + "typed-arena", + "unicode-width", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -2997,6 +3126,16 @@ dependencies = [ "unarray", ] +[[package]] +name = "psm" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" +dependencies = [ + "ar_archive_writer", + "cc", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3017,7 +3156,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3039,7 +3178,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3616,6 +3755,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3806,6 +3955,19 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "stacker" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -3918,13 +4080,33 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -4206,10 +4388,16 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 2.0.18", "utf-8", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.20.1" @@ -4252,6 +4440,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -4724,7 +4918,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper", - "thiserror", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml b/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml index 4b02572c1aad..c8aff0b4f30e 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml @@ -11,5 +11,6 @@ publish = false [dependencies] alloy = { version = "1", features = ["full"] } anyhow = "1" +candid = "0.10" hex = "0.4" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md b/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md index df9a69e50d93..1890ba551030 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md @@ -11,6 +11,24 @@ transaction** whose gas is paid entirely by the minter — first for one deposit address, then **batched** (one transaction sweeping three deposit EOAs, their authorizations riding in the same transaction's authorization list). +It then exercises **both sweep variants** of the design doc's open decision: + +* **Variant A** (`CkSweeper`): the delegate transfers straight to the minter; + sweeps are permissionless-safe. +* **Variant B** (`CkSweeperViaHelper`): the deposit EOAs are re-delegated and + the sweep calls `depositErc20` on the **real helper contract** (`CkDeposit`, + compiled from `../../minter/DepositHelperWithSubaccount.sol`), so each sweep + emits the canonical `ReceivedEthOrErc20` event — asserted to carry the right + IC principal — that the minter's existing deposit pipeline already scrapes + and mints from. Because the principal is a sweep argument, sweeping is + restricted to the minter: the demo ends with an **attacker attempting to + sweep with their own principal and being rejected**, followed by the correct + minter sweep (a plain EIP-1559 transaction — delegation persists). + +Every sweep prints the minter's nonce, the raw signed transaction and the +receipt, and the gas used by each sweep is asserted against hard-coded +constants (the run is fully deterministic). + ## Run Start anvil (its default hardfork is the latest supported one, which includes @@ -37,13 +55,20 @@ cargo run ## Contracts -`contracts/` holds the Solidity sources (`CkSweeper.sol`, the EIP-7702 delegate -and batcher; `MockUSDT.sol`, a USDT-style ERC-20 whose `transfer` returns no -value). The deployment bytecode embedded in the binary lives in `artifacts/`; -regenerate it after changing the contracts with: +`contracts/` holds the Solidity sources (`CkSweeper.sol` and +`CkSweeperViaHelper.sol`, the EIP-7702 delegates and batchers for the two sweep +variants; `MockUSDT.sol`, a USDT-style ERC-20 whose `transfer`/`approve`/ +`transferFrom` return no value). The deployment bytecode embedded in the binary +lives in `artifacts/`, including `CkDeposit.bin.hex` compiled from the real +helper contract `../../minter/DepositHelperWithSubaccount.sol`. Regenerate +after changing the contracts with: ```shell +cp ../../minter/DepositHelperWithSubaccount.sol contracts/ forge build -grep -o '"object":"0x[0-9a-f]*"' out/CkSweeper.sol/CkSweeper.json | head -1 | sed 's/"object":"//;s/"//' > artifacts/CkSweeper.bin.hex -grep -o '"object":"0x[0-9a-f]*"' out/MockUSDT.sol/MockUSDT.json | head -1 | sed 's/"object":"//;s/"//' > artifacts/MockUSDT.bin.hex +for c in CkSweeper CkSweeperViaHelper MockUSDT; do + grep -o '"object":"0x[0-9a-f]*"' out/$c.sol/$c.json | head -1 | sed 's/"object":"//;s/"//' > artifacts/$c.bin.hex +done +grep -o '"object":"0x[0-9a-f]*"' out/DepositHelperWithSubaccount.sol/CkDeposit.json | head -1 | sed 's/"object":"//;s/"//' > artifacts/CkDeposit.bin.hex +rm contracts/DepositHelperWithSubaccount.sol ``` diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkDeposit.bin.hex b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkDeposit.bin.hex new file mode 100644 index 000000000000..e01c67c09b4b --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkDeposit.bin.hex @@ -0,0 +1 @@ +0x60a060405234801561000f575f5ffd5b50604051610a4e380380610a4e833981810160405281019061003191906100c9565b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050506100f4565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100988261006f565b9050919050565b6100a88161008e565b81146100b2575f5ffd5b50565b5f815190506100c38161009f565b92915050565b5f602082840312156100de576100dd61006b565b5b5f6100eb848285016100b5565b91505092915050565b60805161093461011a5f395f818161010f01528181610178015261021401526109345ff3fe608060405260043610610033575f3560e01c806317c819c41461003757806393e5d42a14610053578063db9751af1461007d575b5f5ffd5b610051600480360381019061004c91906105c7565b6100a5565b005b34801561005e575f5ffd5b50610067610175565b6040516100749190610644565b60405180910390f35b348015610088575f5ffd5b506100a3600480360381019061009e91906106ba565b61019c565b005b813373ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167f918adbebdb8f3b36fc337ab76df10b147b2def5c9dd62cb3456d9aeca40e0b07348560405161010592919061073c565b60405180910390a47f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166108fc3490811502906040515f60405180830381858888f19350505050158015610170573d5f5f3e3d5ffd5b505050565b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361020a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610201906107e3565b60405180910390fd5b5f84905061025b337f0000000000000000000000000000000000000000000000000000000000000000868473ffffffffffffffffffffffffffffffffffffffff166102ca909392919063ffffffff16565b823373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167f918adbebdb8f3b36fc337ab76df10b147b2def5c9dd62cb3456d9aeca40e0b0787866040516102bb92919061073c565b60405180910390a45050505050565b610346848573ffffffffffffffffffffffffffffffffffffffff166323b872dd8686866040516024016102ff93929190610801565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061034c565b50505050565b5f610376828473ffffffffffffffffffffffffffffffffffffffff166103e190919063ffffffff16565b90505f81511415801561039a575080806020019051810190610398919061086b565b155b156103dc57826040517f5274afe70000000000000000000000000000000000000000000000000000000081526004016103d39190610644565b60405180910390fd5b505050565b60606103ee83835f6103f6565b905092915050565b60608147101561043d57306040517fcd7860590000000000000000000000000000000000000000000000000000000081526004016104349190610644565b60405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff16848660405161046591906108e8565b5f6040518083038185875af1925050503d805f811461049f576040519150601f19603f3d011682016040523d82523d5f602084013e6104a4565b606091505b50915091506104b48683836104bf565b925050509392505050565b6060826104d4576104cf8261054c565b610544565b5f82511480156104fa57505f8473ffffffffffffffffffffffffffffffffffffffff163b145b1561053c57836040517f9996b3150000000000000000000000000000000000000000000000000000000081526004016105339190610644565b60405180910390fd5b819050610545565b5b9392505050565b5f8151111561055e5780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5ffd5b5f819050919050565b6105a681610594565b81146105b0575f5ffd5b50565b5f813590506105c18161059d565b92915050565b5f5f604083850312156105dd576105dc610590565b5b5f6105ea858286016105b3565b92505060206105fb858286016105b3565b9150509250929050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61062e82610605565b9050919050565b61063e81610624565b82525050565b5f6020820190506106575f830184610635565b92915050565b61066681610624565b8114610670575f5ffd5b50565b5f813590506106818161065d565b92915050565b5f819050919050565b61069981610687565b81146106a3575f5ffd5b50565b5f813590506106b481610690565b92915050565b5f5f5f5f608085870312156106d2576106d1610590565b5b5f6106df87828801610673565b94505060206106f0878288016106a6565b9350506040610701878288016105b3565b9250506060610712878288016105b3565b91505092959194509250565b61072781610687565b82525050565b61073681610594565b82525050565b5f60408201905061074f5f83018561071e565b61075c602083018461072d565b9392505050565b5f82825260208201905092915050565b7f45524332303a206465706f73697445726332302066726f6d20746865207a65725f8201527f6f20616464726573730000000000000000000000000000000000000000000000602082015250565b5f6107cd602983610763565b91506107d882610773565b604082019050919050565b5f6020820190508181035f8301526107fa816107c1565b9050919050565b5f6060820190506108145f830186610635565b6108216020830185610635565b61082e604083018461071e565b949350505050565b5f8115159050919050565b61084a81610836565b8114610854575f5ffd5b50565b5f8151905061086581610841565b92915050565b5f602082840312156108805761087f610590565b5b5f61088d84828501610857565b91505092915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f6108c282610896565b6108cc81856108a0565b93506108dc8185602086016108aa565b80840191505092915050565b5f6108f382846108b8565b91508190509291505056fea2646970667358221220263cd2fa0b93cbb3f839a2b0960aa616a17d29543bd751aa6f489399c0b0c9f364736f6c63430008230033 diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeperViaHelper.bin.hex b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeperViaHelper.bin.hex new file mode 100644 index 000000000000..646b414a65d8 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeperViaHelper.bin.hex @@ -0,0 +1 @@ +0x60e060405234801561000f575f5ffd5b50604051610f4d380380610f4d83398181016040528101906100319190610132565b8173ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff16815250508073ffffffffffffffffffffffffffffffffffffffff1660a08173ffffffffffffffffffffffffffffffffffffffff16815250503073ffffffffffffffffffffffffffffffffffffffff1660c08173ffffffffffffffffffffffffffffffffffffffff16815250505050610170565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610101826100d8565b9050919050565b610111816100f7565b811461011b575f5ffd5b50565b5f8151905061012c81610108565b92915050565b5f5f60408385031215610148576101476100d4565b5b5f6101558582860161011e565b92505060206101668582860161011e565b9150509250929050565b60805160a05160c051610da66101a75f395f61029901525f818161040d015261043401525f8181607201526102440152610da65ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c80635373f03314610038578063dfff6b1d14610054575b5f5ffd5b610052600480360381019061004d919061071e565b610070565b005b61006e60048036038101906100699190610835565b610242565b005b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100f590610900565b60405180910390fd5b858590508888905014801561011857508383905086869050145b610157576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161014e90610968565b60405180910390fd5b5f5f90505b888890508110156102375788888281811061017a57610179610986565b5b905060200201602081019061018f9190610a0d565b73ffffffffffffffffffffffffffffffffffffffff1663dfff6b1d84848a8a868181106101bf576101be610986565b5b905060200201358989878181106101d9576101d8610986565b5b905060200201356040518563ffffffff1660e01b81526004016101ff9493929190610b03565b5f604051808303815f87803b158015610216575f5ffd5b505af1158015610228573d5f5f3e3d5ffd5b5050505080600101905061015c565b505050505050505050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102e757507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b610326576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161031d90610900565b60405180910390fd5b5f5f90505b848490508110156104f2575f85858381811061034a57610349610986565b5b905060200201602081019061035f9190610a0d565b73ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b81526004016103979190610b50565b602060405180830381865afa1580156103b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103d69190610b9c565b90505f8111156104e6576104328686848181106103f6576103f5610986565b5b905060200201602081019061040b9190610a0d565b7f0000000000000000000000000000000000000000000000000000000000000000836104f9565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663db9751af87878581811061048157610480610986565b5b90506020020160208101906104969190610a0d565b8387876040518563ffffffff1660e01b81526004016104b89493929190610bd6565b5f604051808303815f87803b1580156104cf575f5ffd5b505af11580156104e1573d5f5f3e3d5ffd5b505050505b5080600101905061032b565b5050505050565b5f5f8473ffffffffffffffffffffffffffffffffffffffff168484604051602401610525929190610c19565b6040516020818303038152906040527f095ea7b3000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516105af9190610c92565b5f604051808303815f865af19150503d805f81146105e8576040519150601f19603f3d011682016040523d82523d5f602084013e6105ed565b606091505b509150915081801561061a57505f815114806106195750808060200190518101906106189190610cdd565b5b5b610659576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161065090610d52565b60405180910390fd5b5050505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f84011261068957610688610668565b5b8235905067ffffffffffffffff8111156106a6576106a561066c565b5b6020830191508360208202830111156106c2576106c1610670565b5b9250929050565b5f5f83601f8401126106de576106dd610668565b5b8235905067ffffffffffffffff8111156106fb576106fa61066c565b5b60208301915083602082028301111561071757610716610670565b5b9250929050565b5f5f5f5f5f5f5f5f6080898b03121561073a57610739610660565b5b5f89013567ffffffffffffffff81111561075757610756610664565b5b6107638b828c01610674565b9850985050602089013567ffffffffffffffff81111561078657610785610664565b5b6107928b828c016106c9565b9650965050604089013567ffffffffffffffff8111156107b5576107b4610664565b5b6107c18b828c016106c9565b9450945050606089013567ffffffffffffffff8111156107e4576107e3610664565b5b6107f08b828c01610674565b92509250509295985092959890939650565b5f819050919050565b61081481610802565b811461081e575f5ffd5b50565b5f8135905061082f8161080b565b92915050565b5f5f5f5f6060858703121561084d5761084c610660565b5b5f85013567ffffffffffffffff81111561086a57610869610664565b5b61087687828801610674565b9450945050602061088987828801610821565b925050604061089a87828801610821565b91505092959194509250565b5f82825260208201905092915050565b7f63616c6c6572206973206e6f7420746865206d696e74657200000000000000005f82015250565b5f6108ea6018836108a6565b91506108f5826108b6565b602082019050919050565b5f6020820190508181035f830152610917816108de565b9050919050565b7f6c656e677468206d69736d6174636800000000000000000000000000000000005f82015250565b5f610952600f836108a6565b915061095d8261091e565b602082019050919050565b5f6020820190508181035f83015261097f81610946565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6109dc826109b3565b9050919050565b6109ec816109d2565b81146109f6575f5ffd5b50565b5f81359050610a07816109e3565b92915050565b5f60208284031215610a2257610a21610660565b5b5f610a2f848285016109f9565b91505092915050565b5f82825260208201905092915050565b5f819050919050565b610a5a816109d2565b82525050565b5f610a6b8383610a51565b60208301905092915050565b5f610a8560208401846109f9565b905092915050565b5f602082019050919050565b5f610aa48385610a38565b9350610aaf82610a48565b805f5b85811015610ae757610ac48284610a77565b610ace8882610a60565b9750610ad983610a8d565b925050600181019050610ab2565b5085925050509392505050565b610afd81610802565b82525050565b5f6060820190508181035f830152610b1c818688610a99565b9050610b2b6020830185610af4565b610b386040830184610af4565b95945050505050565b610b4a816109d2565b82525050565b5f602082019050610b635f830184610b41565b92915050565b5f819050919050565b610b7b81610b69565b8114610b85575f5ffd5b50565b5f81519050610b9681610b72565b92915050565b5f60208284031215610bb157610bb0610660565b5b5f610bbe84828501610b88565b91505092915050565b610bd081610b69565b82525050565b5f608082019050610be95f830187610b41565b610bf66020830186610bc7565b610c036040830185610af4565b610c106060830184610af4565b95945050505050565b5f604082019050610c2c5f830185610b41565b610c396020830184610bc7565b9392505050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f610c6c82610c40565b610c768185610c4a565b9350610c86818560208601610c54565b80840191505092915050565b5f610c9d8284610c62565b915081905092915050565b5f8115159050919050565b610cbc81610ca8565b8114610cc6575f5ffd5b50565b5f81519050610cd781610cb3565b92915050565b5f60208284031215610cf257610cf1610660565b5b5f610cff84828501610cc9565b91505092915050565b7f617070726f7665206661696c65640000000000000000000000000000000000005f82015250565b5f610d3c600e836108a6565b9150610d4782610d08565b602082019050919050565b5f6020820190508181035f830152610d6981610d30565b905091905056fea2646970667358221220f85b472306107bdf2ff8076ead503d7e2ee0413f08df91d7aceae4d76e586d5d64736f6c63430008230033 diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex index 8441f6c041b8..d38e6581cf1a 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex @@ -1 +1 @@ -0x60a060405234801561000f575f5ffd5b5060405161086f38038061086f83398181016040528101906100319190610177565b805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555080608081815250508173ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516100d791906101c4565b60405180910390a350506101dd565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610113826100ea565b9050919050565b61012381610109565b811461012d575f5ffd5b50565b5f8151905061013e8161011a565b92915050565b5f819050919050565b61015681610144565b8114610160575f5ffd5b50565b5f815190506101718161014d565b92915050565b5f5f6040838503121561018d5761018c6100e6565b5b5f61019a85828601610130565b92505060206101ab85828601610163565b9150509250929050565b6101be81610144565b82525050565b5f6020820190506101d75f8301846101b5565b92915050565b60805161067a6101f55f395f610163015261067a5ff3fe608060405234801561000f575f5ffd5b5060043610610060575f3560e01c806306fdde031461006457806318160ddd14610082578063313ce567146100a057806370a08231146100be57806395d89b41146100ee578063a9059cbb1461010c575b5f5ffd5b61006c610128565b60405161007991906103d3565b60405180910390f35b61008a610161565b604051610097919061040b565b60405180910390f35b6100a8610185565b6040516100b5919061043f565b60405180910390f35b6100d860048036038101906100d391906104b6565b61018a565b6040516100e5919061040b565b60405180910390f35b6100f661019e565b60405161010391906103d3565b60405180910390f35b6101266004803603810190610121919061050b565b6101d7565b005b6040518060400160405280600f81526020017f4d6f636b2054657468657220555344000000000000000000000000000000000081525081565b7f000000000000000000000000000000000000000000000000000000000000000081565b600681565b5f602052805f5260405f205f915090505481565b6040518060400160405280600481526020017f555344540000000000000000000000000000000000000000000000000000000081525081565b805f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20541015610256576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161024d90610593565b60405180910390fd5b805f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546102a191906105de565b92505081905550805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546102f39190610611565b925050819055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610357919061040b565b60405180910390a35050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6103a582610363565b6103af818561036d565b93506103bf81856020860161037d565b6103c88161038b565b840191505092915050565b5f6020820190508181035f8301526103eb818461039b565b905092915050565b5f819050919050565b610405816103f3565b82525050565b5f60208201905061041e5f8301846103fc565b92915050565b5f60ff82169050919050565b61043981610424565b82525050565b5f6020820190506104525f830184610430565b92915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6104858261045c565b9050919050565b6104958161047b565b811461049f575f5ffd5b50565b5f813590506104b08161048c565b92915050565b5f602082840312156104cb576104ca610458565b5b5f6104d8848285016104a2565b91505092915050565b6104ea816103f3565b81146104f4575f5ffd5b50565b5f81359050610505816104e1565b92915050565b5f5f6040838503121561052157610520610458565b5b5f61052e858286016104a2565b925050602061053f858286016104f7565b9150509250929050565b7f696e73756666696369656e742062616c616e63650000000000000000000000005f82015250565b5f61057d60148361036d565b915061058882610549565b602082019050919050565b5f6020820190508181035f8301526105aa81610571565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6105e8826103f3565b91506105f3836103f3565b925082820390508181111561060b5761060a6105b1565b5b92915050565b5f61061b826103f3565b9150610626836103f3565b925082820190508082111561063e5761063d6105b1565b5b9291505056fea26469706673582212205ab6964be15449ed2c0cf9fc3e98bd4a0f4c7bf895cbccdf9c4b0262ede5479b64736f6c63430008230033 +0x60a060405234801561000f575f5ffd5b50604051610f06380380610f0683398181016040528101906100319190610177565b805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555080608081815250508173ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516100d791906101c4565b60405180910390a350506101dd565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610113826100ea565b9050919050565b61012381610109565b811461012d575f5ffd5b50565b5f8151905061013e8161011a565b92915050565b5f819050919050565b61015681610144565b8114610160575f5ffd5b50565b5f815190506101718161014d565b92915050565b5f5f6040838503121561018d5761018c6100e6565b5b5f61019a85828601610130565b92505060206101ab85828601610163565b9150509250929050565b6101be81610144565b82525050565b5f6020820190506101d75f8301846101b5565b92915050565b608051610d116101f55f395f6103a60152610d115ff3fe608060405234801561000f575f5ffd5b5060043610610091575f3560e01c8063313ce56711610064578063313ce5671461010957806370a082311461012757806395d89b4114610157578063a9059cbb14610175578063dd62ed3e1461019157610091565b806306fdde0314610095578063095ea7b3146100b357806318160ddd146100cf57806323b872dd146100ed575b5f5ffd5b61009d6101c1565b6040516100aa919061090c565b60405180910390f35b6100cd60048036038101906100c891906109bd565b6101fa565b005b6100d76103a4565b6040516100e49190610a0a565b60405180910390f35b61010760048036038101906101029190610a23565b6103c8565b005b61011161069e565b60405161011e9190610a8e565b60405180910390f35b610141600480360381019061013c9190610aa7565b6106a3565b60405161014e9190610a0a565b60405180910390f35b61015f6106b7565b60405161016c919061090c565b60405180910390f35b61018f600480360381019061018a91906109bd565b6106f0565b005b6101ab60048036038101906101a69190610ad2565b61087c565b6040516101b89190610a0a565b60405180910390f35b6040518060400160405280600f81526020017f4d6f636b2054657468657220555344000000000000000000000000000000000081525081565b5f81148061027f57505f60015f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054145b6102be576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102b590610b5a565b60405180910390fd5b8060015f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516103989190610a0a565b60405180910390a35050565b7f000000000000000000000000000000000000000000000000000000000000000081565b8060015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20541015610483576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161047a90610bc2565b60405180910390fd5b805f5f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20541015610502576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104f990610c2a565b60405180910390fd5b8060015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105899190610c75565b92505081905550805f5f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105db9190610c75565b92505081905550805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461062d9190610ca8565b925050819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516106919190610a0a565b60405180910390a3505050565b600681565b5f602052805f5260405f205f915090505481565b6040518060400160405280600481526020017f555344540000000000000000000000000000000000000000000000000000000081525081565b805f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561076f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161076690610c2a565b60405180910390fd5b805f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546107ba9190610c75565b92505081905550805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461080c9190610ca8565b925050819055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516108709190610a0a565b60405180910390a35050565b6001602052815f5260405f20602052805f5260405f205f91509150505481565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6108de8261089c565b6108e881856108a6565b93506108f88185602086016108b6565b610901816108c4565b840191505092915050565b5f6020820190508181035f83015261092481846108d4565b905092915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61095982610930565b9050919050565b6109698161094f565b8114610973575f5ffd5b50565b5f8135905061098481610960565b92915050565b5f819050919050565b61099c8161098a565b81146109a6575f5ffd5b50565b5f813590506109b781610993565b92915050565b5f5f604083850312156109d3576109d261092c565b5b5f6109e085828601610976565b92505060206109f1858286016109a9565b9150509250929050565b610a048161098a565b82525050565b5f602082019050610a1d5f8301846109fb565b92915050565b5f5f5f60608486031215610a3a57610a3961092c565b5b5f610a4786828701610976565b9350506020610a5886828701610976565b9250506040610a69868287016109a9565b9150509250925092565b5f60ff82169050919050565b610a8881610a73565b82525050565b5f602082019050610aa15f830184610a7f565b92915050565b5f60208284031215610abc57610abb61092c565b5b5f610ac984828501610976565b91505092915050565b5f5f60408385031215610ae857610ae761092c565b5b5f610af585828601610976565b9250506020610b0685828601610976565b9150509250929050565b7f726573657420616c6c6f77616e636520746f20302066697273740000000000005f82015250565b5f610b44601a836108a6565b9150610b4f82610b10565b602082019050919050565b5f6020820190508181035f830152610b7181610b38565b9050919050565b7f696e73756666696369656e7420616c6c6f77616e6365000000000000000000005f82015250565b5f610bac6016836108a6565b9150610bb782610b78565b602082019050919050565b5f6020820190508181035f830152610bd981610ba0565b9050919050565b7f696e73756666696369656e742062616c616e63650000000000000000000000005f82015250565b5f610c146014836108a6565b9150610c1f82610be0565b602082019050919050565b5f6020820190508181035f830152610c4181610c08565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610c7f8261098a565b9150610c8a8361098a565b9250828203905081811115610ca257610ca1610c48565b5b92915050565b5f610cb28261098a565b9150610cbd8361098a565b9250828201905080821115610cd557610cd4610c48565b5b9291505056fea264697066735822122062325128908826748d8550bd08c25a9c48f69baf2faaa5381c6ea50e3e8c888a64736f6c63430008230033 diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeperViaHelper.sol b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeperViaHelper.sol new file mode 100644 index 000000000000..0ff91092b461 --- /dev/null +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeperViaHelper.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +interface ICkDeposit { + function depositErc20(address erc20Address, uint256 amount, bytes32 principal, bytes32 subaccount) external; +} + +interface IErc20Balance { + function balanceOf(address account) external view returns (uint256); +} + +/// EIP-7702 delegate variant that sweeps by calling the existing ckETH helper +/// contract (CkDeposit, see minter/DepositHelperWithSubaccount.sol), so that +/// the sweep emits the canonical ReceivedEthOrErc20 event consumed by the +/// minter's unchanged deposit pipeline. +/// The IC principal/subaccount are caller-supplied, so sweeping MUST NOT be +/// permissionless (anyone could credit deposits to their own principal): +/// only the minter, directly or through the batch entry point of the deployed +/// instance (SELF), may sweep. +contract CkSweeperViaHelper { + address private immutable MINTER; + address private immutable HELPER; + address private immutable SELF; + + constructor(address minter, address helper) { + MINTER = minter; + HELPER = helper; + SELF = address(this); + } + + function sweepErc20(address[] calldata tokens, bytes32 principal, bytes32 subaccount) external { + require(msg.sender == MINTER || msg.sender == SELF, "caller is not the minter"); + for (uint256 i = 0; i < tokens.length; ++i) { + uint256 balance = IErc20Balance(tokens[i]).balanceOf(address(this)); + if (balance > 0) { + _safeApprove(tokens[i], HELPER, balance); + ICkDeposit(HELPER).depositErc20(tokens[i], balance, principal, subaccount); + } + } + } + + /// Batch entry point on the deployed instance: sweeps many delegated + /// deposit EOAs in a single transaction, each towards its own IC account. + function sweepErc20Batch( + address[] calldata depositAddresses, + bytes32[] calldata principals, + bytes32[] calldata subaccounts, + address[] calldata tokens + ) external { + require(msg.sender == MINTER, "caller is not the minter"); + require( + depositAddresses.length == principals.length && principals.length == subaccounts.length, + "length mismatch" + ); + for (uint256 i = 0; i < depositAddresses.length; ++i) { + CkSweeperViaHelper(depositAddresses[i]).sweepErc20(tokens, principals[i], subaccounts[i]); + } + } + + /// Tolerates non-standard ERC-20s such as USDT whose approve returns no value. + function _safeApprove(address token, address spender, uint256 value) private { + (bool ok, bytes memory data) = + token.call(abi.encodeWithSignature("approve(address,uint256)", spender, value)); + require(ok && (data.length == 0 || abi.decode(data, (bool))), "approve failed"); + } +} diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol index ee0026c8557d..627241d86258 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol @@ -1,15 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -/// Mimics USDT's non-standard ERC-20: `transfer` returns no value. +/// Mimics USDT's non-standard ERC-20: `transfer`, `approve` and `transferFrom` +/// return no value, and `approve` requires resetting the allowance to zero first. contract MockUSDT { string public constant name = "Mock Tether USD"; string public constant symbol = "USDT"; uint8 public constant decimals = 6; uint256 public immutable totalSupply; mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); constructor(address initialHolder, uint256 initialSupply) { balanceOf[initialHolder] = initialSupply; @@ -23,4 +26,19 @@ contract MockUSDT { balanceOf[to] += value; emit Transfer(msg.sender, to, value); } + + function approve(address spender, uint256 value) external { + require(value == 0 || allowance[msg.sender][spender] == 0, "reset allowance to 0 first"); + allowance[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + } + + function transferFrom(address from, address to, uint256 value) external { + require(allowance[from][msg.sender] >= value, "insufficient allowance"); + require(balanceOf[from] >= value, "insufficient balance"); + allowance[from][msg.sender] -= value; + balanceOf[from] -= value; + balanceOf[to] += value; + emit Transfer(from, to, value); + } } diff --git a/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs b/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs index 844a2de2f805..b33eeba44d98 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs +++ b/rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs @@ -10,10 +10,20 @@ //! deposit addresses (the CEX pays that gas). //! 4) the minter sweeps a deposit address in ONE type-0x04 (EIP-7702) //! transaction: authorization signed by the deposit EOA + call to -//! sweepErc20, gas paid by the minter. +//! sweepErc20, gas paid by the minter (variant A: direct sweep). //! 5) batched sweep: ONE transaction targets several deposit EOAs at once //! via CkSweeper.sweepErc20Batch, their authorizations riding in the same //! transaction's authorization list. +//! 6) variant B setup: the REAL helper contract (CkDeposit, compiled from +//! minter/DepositHelperWithSubaccount.sol) and the CkSweeperViaHelper +//! delegate are deployed; the CEX sends a second round of deposits. +//! 7) variant B sweeps: the deposit EOAs are re-delegated (new +//! authorizations) to CkSweeperViaHelper, which sweeps by calling the +//! helper's depositErc20, so each sweep emits the canonical +//! ReceivedEthOrErc20 event (with the right IC principal) that the +//! minter's existing deposit pipeline already scrapes and mints from. +//! 8) attack: someone other than the minter tries to sweep (passing their +//! own principal) and is rejected; the deposit is then swept correctly. //! //! Requires a dev node supporting EIP-7702 (Ethereum mainnet since the Pectra //! upgrade, May 2025), e.g. @@ -26,7 +36,7 @@ use alloy::{ consensus::{Transaction, TxEnvelope}, eips::{eip2718::Encodable2718, eip7702::Authorization, eip7702::SignedAuthorization}, network::{EthereumWallet, TransactionBuilder, TransactionBuilder7702}, - primitives::{Address, U256, keccak256}, + primitives::{Address, FixedBytes, U256, keccak256}, providers::{DynProvider, Provider, ProviderBuilder}, rpc::types::{TransactionReceipt, TransactionRequest}, signers::{SignerSync, local::PrivateKeySigner}, @@ -34,6 +44,7 @@ use alloy::{ sol_types::{SolCall, SolConstructor, SolEvent}, }; use anyhow::{Context, Result, ensure}; +use candid::Principal; sol! { #[sol(rpc)] @@ -43,6 +54,28 @@ sol! { function sweepErc20Batch(address[] calldata depositAddresses, address[] calldata tokens) external; } + #[sol(rpc)] + contract CkSweeperViaHelper { + constructor(address minter, address helper); + function sweepErc20(address[] calldata tokens, bytes32 principal, bytes32 subaccount) external; + function sweepErc20Batch(address[] calldata depositAddresses, bytes32[] calldata principals, bytes32[] calldata subaccounts, address[] calldata tokens) external; + } + + /// The existing ckETH helper smart contract + /// (minter/DepositHelperWithSubaccount.sol). + #[sol(rpc)] + contract CkDeposit { + constructor(address _minterAddress); + function getMinterAddress() public view returns (address); + event ReceivedEthOrErc20( + address indexed erc20ContractAddress, + address indexed owner, + uint256 amount, + bytes32 indexed principal, + bytes32 subaccount + ); + } + #[sol(rpc)] contract MockUSDT { constructor(address initialHolder, uint256 initialSupply); @@ -53,19 +86,25 @@ sol! { } const CKSWEEPER_BYTECODE: &str = include_str!("../artifacts/CkSweeper.bin.hex"); +const CKSWEEPER_VIA_HELPER_BYTECODE: &str = include_str!("../artifacts/CkSweeperViaHelper.bin.hex"); +const CKDEPOSIT_BYTECODE: &str = include_str!("../artifacts/CkDeposit.bin.hex"); const MOCKUSDT_BYTECODE: &str = include_str!("../artifacts/MockUSDT.bin.hex"); -// Anvil's first two well-known dev accounts. +// Anvil's first three well-known dev accounts. const MINTER_PRIVATE_KEY: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; const CEX_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; +const ATTACKER_PRIVATE_KEY: &str = + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"; const USDT_SUPPLY: u64 = 1_000_000_000_000; // 1M USDT (6 decimals) // The demo is fully deterministic (fixed keys, fixed contracts, fresh chain), -// so the gas used by both sweep transactions is a constant. -const SINGLE_SWEEP_GAS_USED: u64 = 66_889; -const BATCH_SWEEP_GAS_USED: u64 = 118_982; +// so the gas used by each sweep transaction is a constant. +const SINGLE_SWEEP_GAS_USED: u64 = 66_854; +const BATCH_SWEEP_GAS_USED: u64 = 118_876; +const SINGLE_SWEEP_VIA_HELPER_GAS_USED: u64 = 82_207; +const BATCH_SWEEP_VIA_HELPER_GAS_USED: u64 = 164_746; fn step(title: &str) { println!("\n== {title}"); @@ -89,13 +128,26 @@ fn derive_deposit_signer(principal: &str) -> PrivateKeySigner { PrivateKeySigner::from_bytes(&seed).expect("valid derived key") } +/// Encodes an IC principal as the bytes32 expected by the helper contract: +/// byte 0 is the principal length, followed by the principal bytes. +fn encode_principal(principal_text: &str) -> FixedBytes<32> { + let principal = Principal::from_text(principal_text).expect("valid principal"); + let bytes = principal.as_slice(); + let mut encoded = [0u8; 32]; + encoded[0] = bytes.len() as u8; + encoded[1..=bytes.len()].copy_from_slice(bytes); + FixedBytes::from(encoded) +} + /// Fills chain id, nonce, fees and gas, then signs with the given wallet, -/// without sending. +/// without sending. An explicit gas limit skips estimation (needed for +/// transactions that are expected to revert). async fn fill_and_sign( provider: &DynProvider, wallet: &EthereumWallet, from: Address, tx: TransactionRequest, + gas_limit: Option, ) -> Result { let fees = provider.estimate_eip1559_fees().await?; let mut tx = tx @@ -104,7 +156,10 @@ async fn fill_and_sign( .with_nonce(provider.get_transaction_count(from).await?) .with_max_fee_per_gas(fees.max_fee_per_gas) .with_max_priority_fee_per_gas(fees.max_priority_fee_per_gas); - let gas_limit = provider.estimate_gas(tx.clone()).await?; + let gas_limit = match gas_limit { + Some(gas_limit) => gas_limit, + None => provider.estimate_gas(tx.clone()).await?, + }; tx = tx.with_gas_limit(gas_limit); Ok(tx.build(wallet).await?) } @@ -124,6 +179,7 @@ async fn deploy( wallet, deployer_address, TransactionRequest::default().with_deploy_code(code), + None, ) .await?; show(&format!("{deployer} nonce (deploy):"), envelope.nonce()); @@ -139,11 +195,12 @@ fn sign_delegation( deposit_signer: &PrivateKeySigner, sweeper: Address, chain_id: u64, + nonce: u64, ) -> Result { let authorization = Authorization { chain_id: U256::from(chain_id), address: sweeper, - nonce: 0, + nonce, }; let signature = deposit_signer.sign_hash_sync(&authorization.signature_hash())?; Ok(authorization.into_signed(signature)) @@ -159,7 +216,7 @@ async fn send_and_print_sweep_transaction( tx: TransactionRequest, authorizations: &[SignedAuthorization], ) -> Result { - let envelope = fill_and_sign(minter_provider, minter_wallet, minter, tx).await?; + let envelope = fill_and_sign(minter_provider, minter_wallet, minter, tx, None).await?; show("minter nonce (sweep):", envelope.nonce()); show( "raw signed transaction:", @@ -172,11 +229,16 @@ async fn send_and_print_sweep_transaction( .await?; show("transaction hash:", receipt.transaction_hash); + let transaction_type = receipt.transaction_type() as u8; show( "transaction type:", format!( - "{} (4 = EIP-7702 SetCode)", - receipt.transaction_type() as u8 + "{transaction_type}{}", + match transaction_type { + 4 => " (EIP-7702 SetCode)", + 2 => " (EIP-1559, no authorization needed anymore)", + _ => "", + } ), ); show("from (pays gas):", receipt.from); @@ -209,6 +271,19 @@ async fn send_and_print_sweep_transaction( ), ); } + if let Ok(received) = CkDeposit::ReceivedEthOrErc20::decode_log(&log.inner) { + show( + "ReceivedEthOrErc20 event:", + format!( + "token {}, owner {}, amount {}, principal {}, subaccount {}", + received.erc20ContractAddress, + received.owner, + received.amount / U256::from(1_000_000), + received.principal, + received.subaccount + ), + ); + } } Ok(receipt) } @@ -266,12 +341,9 @@ async fn main() -> Result<()> { show("MockUSDT (USDT-style ERC-20, 6 decimals):", usdt_address); step("1) Minter derives user-specific deposit addresses"); - let principals = [ - "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", - "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe", - "6b4pv-sbpcm-4nsnw-4iplt-t46wy-hbdla-vhemq-lvhlv-tghdk-k6gao-mae", - "5nl2b-p6c6f-h4o7v-erwra-ceapp-ai4fw-ze2cs-6xsyq-mnhwq-kkwoa-yqe", - ]; + let principals: Vec = (0u8..4) + .map(|i| Principal::self_authenticating([i]).to_text()) + .collect(); let deposit_signers: Vec = principals .iter() .map(|p| derive_deposit_signer(p)) @@ -308,8 +380,8 @@ async fn main() -> Result<()> { } ok("deposit addresses still have 0 ETH (cannot pay gas themselves)"); - step("4) Minter sweeps ONE deposit address in ONE EIP-7702 transaction"); - let single_authorization = sign_delegation(&deposit_signers[0], sweeper_address, chain_id)?; + step("4) Variant A: minter sweeps ONE deposit address in ONE EIP-7702 transaction"); + let single_authorization = sign_delegation(&deposit_signers[0], sweeper_address, chain_id, 0)?; let single_sweep = TransactionRequest::default() .with_to(deposit_addresses[0]) .with_input( @@ -342,10 +414,10 @@ async fn main() -> Result<()> { (21000 base + 25000 authorization + ~21000 delegated ERC-20 sweep)" )); - step("5) Batched sweep: ONE transaction targets the 3 remaining deposit EOAs"); + step("5) Variant A batched: ONE transaction targets the 3 remaining deposit EOAs"); let batch_authorizations: Vec = deposit_signers[1..] .iter() - .map(|signer| sign_delegation(signer, sweeper_address, chain_id)) + .map(|signer| sign_delegation(signer, sweeper_address, chain_id, 0)) .collect::>()?; let batch_targets = deposit_addresses[1..].to_vec(); let batch_sweep = TransactionRequest::default() @@ -386,6 +458,232 @@ async fn main() -> Result<()> { (3 separate sweeps); ~{marginal} gas per additional EOA" )); + step("6) Variant B setup: the existing helper contract and its sweeper delegate"); + let helper_address = deploy( + &minter_provider, + &minter_wallet, + "minter", + minter, + CKDEPOSIT_BYTECODE, + CkDeposit::constructorCall { + _minterAddress: minter, + } + .abi_encode(), + ) + .await?; + let via_helper_address = deploy( + &minter_provider, + &minter_wallet, + "minter", + minter, + CKSWEEPER_VIA_HELPER_BYTECODE, + CkSweeperViaHelper::constructorCall { + minter, + helper: helper_address, + } + .abi_encode(), + ) + .await?; + let helper = CkDeposit::new(helper_address, minter_provider.clone()); + ensure!(helper.getMinterAddress().call().await? == minter); + show( + "CkDeposit helper (DepositHelperWithSubaccount):", + helper_address, + ); + show("CkSweeperViaHelper delegate + batcher:", via_helper_address); + let amounts_b: [u64; 4] = [150_000_000, 80_000_000, 60_000_000, 40_000_000]; + for (address, amount) in deposit_addresses.iter().zip(amounts_b) { + usdt_as_cex + .transfer(*address, U256::from(amount)) + .send() + .await? + .get_receipt() + .await?; + show( + &format!("CEX -> {address}:"), + format!("{} USDT", amount / 1_000_000), + ); + } + + step("7) Variant B: sweeps go through the helper, emitting ReceivedEthOrErc20"); + // Each deposit EOA re-delegates to CkSweeperViaHelper: the applied variant-A + // authorization incremented its nonce to 1. + let re_delegation = sign_delegation(&deposit_signers[0], via_helper_address, chain_id, 1)?; + let via_helper_sweep = TransactionRequest::default() + .with_to(deposit_addresses[0]) + .with_input( + CkSweeperViaHelper::sweepErc20Call { + tokens: vec![usdt_address], + principal: encode_principal(&principals[0]), + subaccount: FixedBytes::ZERO, + } + .abi_encode(), + ) + .with_authorization_list(vec![re_delegation.clone()]); + let via_helper_receipt = send_and_print_sweep_transaction( + &minter_provider, + &minter_wallet, + minter, + via_helper_sweep, + &[re_delegation], + ) + .await?; + + ensure!(via_helper_receipt.status(), "sweep via helper reverted"); + ensure!(usdt.balanceOf(deposit_addresses[0]).call().await?.is_zero()); + let received = via_helper_receipt + .logs() + .iter() + .find_map(|log| CkDeposit::ReceivedEthOrErc20::decode_log(&log.inner).ok()) + .context("no ReceivedEthOrErc20 event")?; + ensure!(received.address == helper_address); + ensure!(received.erc20ContractAddress == usdt_address); + ensure!(received.owner == deposit_addresses[0]); + ensure!(received.amount == U256::from(amounts_b[0])); + ensure!(received.principal == encode_principal(&principals[0])); + ensure!(received.subaccount == FixedBytes::ZERO); + ok( + "the sweep emitted the canonical ReceivedEthOrErc20 event from the helper, \ + carrying the right IC principal: the minter's existing deposit pipeline \ + scrapes and mints from it unchanged", + ); + let via_helper_gas = via_helper_receipt.gas_used; + ensure!( + via_helper_gas == SINGLE_SWEEP_VIA_HELPER_GAS_USED, + "unexpected sweep-via-helper gas: {via_helper_gas}, \ + expected {SINGLE_SWEEP_VIA_HELPER_GAS_USED}" + ); + ok(&format!( + "gas used is exactly {SINGLE_SWEEP_VIA_HELPER_GAS_USED} \ + (+{} vs the direct sweep: approve + transferFrom + event)", + SINGLE_SWEEP_VIA_HELPER_GAS_USED - SINGLE_SWEEP_GAS_USED + )); + + // Batched variant B: re-delegate the 3 remaining EOAs in one transaction; + // EOAs 1 and 2 are swept, EOA 3 is only re-delegated (swept in step 8). + let re_delegations: Vec = deposit_signers[1..] + .iter() + .map(|signer| sign_delegation(signer, via_helper_address, chain_id, 1)) + .collect::>()?; + let via_helper_batch = TransactionRequest::default() + .with_to(via_helper_address) + .with_input( + CkSweeperViaHelper::sweepErc20BatchCall { + depositAddresses: deposit_addresses[1..3].to_vec(), + principals: principals[1..3] + .iter() + .map(|p| encode_principal(p)) + .collect(), + subaccounts: vec![FixedBytes::ZERO; 2], + tokens: vec![usdt_address], + } + .abi_encode(), + ) + .with_authorization_list(re_delegations.clone()); + let via_helper_batch_receipt = send_and_print_sweep_transaction( + &minter_provider, + &minter_wallet, + minter, + via_helper_batch, + &re_delegations, + ) + .await?; + ensure!( + via_helper_batch_receipt.status(), + "batched sweep via helper reverted" + ); + let received_events: Vec<_> = via_helper_batch_receipt + .logs() + .iter() + .filter_map(|log| CkDeposit::ReceivedEthOrErc20::decode_log(&log.inner).ok()) + .collect(); + ensure!(received_events.len() == 2); + for (i, event) in received_events.iter().enumerate() { + ensure!(event.owner == deposit_addresses[i + 1]); + ensure!(event.principal == encode_principal(&principals[i + 1])); + } + let via_helper_batch_gas = via_helper_batch_receipt.gas_used; + ensure!( + via_helper_batch_gas == BATCH_SWEEP_VIA_HELPER_GAS_USED, + "unexpected batched sweep-via-helper gas: {via_helper_batch_gas}, \ + expected {BATCH_SWEEP_VIA_HELPER_GAS_USED}" + ); + ok(&format!( + "batched sweep through the helper: 2 canonical deposit events, \ + gas used is exactly {BATCH_SWEEP_VIA_HELPER_GAS_USED} \ + (incl. the deferred re-delegation of the 4th deposit address)" + )); + + step("8) Attack: someone other than the minter tries to sweep"); + // Deposit EOA 3 is already delegated to CkSweeperViaHelper (step 7) but not + // yet swept: it still holds 40 USDT. The attacker tries to credit that + // deposit to their own IC principal. + let attacker_signer: PrivateKeySigner = ATTACKER_PRIVATE_KEY.parse()?; + let attacker = attacker_signer.address(); + let attacker_wallet = EthereumWallet::from(attacker_signer); + let attacker_provider = ProviderBuilder::new() + .wallet(attacker_wallet.clone()) + .connect_http(rpc_url.parse()?) + .erased(); + let attacker_principal = "2vxsx-fae"; + show("attacker (EOA):", attacker); + let attack = TransactionRequest::default() + .with_to(deposit_addresses[3]) + .with_input( + CkSweeperViaHelper::sweepErc20Call { + tokens: vec![usdt_address], + principal: encode_principal(attacker_principal), + subaccount: FixedBytes::ZERO, + } + .abi_encode(), + ); + // Gas estimation would already fail with "caller is not the minter", so the + // attacker forces an explicit gas limit to get the transaction on chain. + let attack_envelope = fill_and_sign( + &attacker_provider, + &attacker_wallet, + attacker, + attack, + Some(300_000), + ) + .await?; + let attack_receipt = attacker_provider + .send_tx_envelope(attack_envelope) + .await? + .get_receipt() + .await?; + ensure!( + !attack_receipt.status(), + "attacker's sweep should have reverted" + ); + ensure!(usdt.balanceOf(deposit_addresses[3]).call().await? == U256::from(amounts_b[3])); + ok("the attacker's sweep reverted (caller is not the minter); funds untouched"); + + // The minter sweeps it correctly: the delegation is already installed, so no + // authorization is needed anymore. + let final_sweep = TransactionRequest::default() + .with_to(deposit_addresses[3]) + .with_input( + CkSweeperViaHelper::sweepErc20Call { + tokens: vec![usdt_address], + principal: encode_principal(&principals[3]), + subaccount: FixedBytes::ZERO, + } + .abi_encode(), + ); + let final_receipt = send_and_print_sweep_transaction( + &minter_provider, + &minter_wallet, + minter, + final_sweep, + &[], + ) + .await?; + ensure!(final_receipt.status(), "final sweep reverted"); + let grand_total: u64 = amounts.iter().sum::() + amounts_b.iter().sum::(); + ensure!(usdt.balanceOf(minter).call().await? == U256::from(grand_total)); + ok("the minter swept it with the right principal; it now holds all 805 USDT"); + println!("\nDemo completed successfully."); Ok(()) } From a7e7e8385492a51617ff6d7111ed1bec742da4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 15:37:27 +0000 Subject: [PATCH 08/13] docs: no per-registration spending by the minter (anti-DoS), R13 Registering a deposit address must trigger no tECDSA signature and no Ethereum transaction: registrations are free for callers, so any eager per-address spending would let an attacker drain the minter's cycles and ETH. Delegation is signed and the sweep submitted only after a balance of a supported token >= the per-token minimum has been observed at the registered address, and balance scanning itself stays claim-driven/bounded since the registered set is attacker-inflatable. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 36 +++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index 45862abdb46e..86f9dbbfd398 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -83,6 +83,12 @@ The design is delivered in two phases: credited-but-unswept balances per token, delegation status, and sweep activity. * `R10`: Withdrawals (ckERC20 → ERC-20 and ckETH → ETH) are unaffected: they continue to be served from the minter's main address and its existing nonce sequence. +* `R13`: Registering a deposit address (`get_deposit_address`) triggers no + threshold-ECDSA signature and no Ethereum transaction. The minter only signs a + delegation and sweeps an address after having observed there a balance of a + supported token of at least the per-token minimum deposit amount. (Registrations + are free for callers; anything the minter spends per registration is a DoS vector + on its cycles and ETH.) ### Phase 2 (ckETH) @@ -161,10 +167,21 @@ The design is delivered in two phases: cheap; continuously scraping `Transfer` logs for an unbounded, growing set of addresses is not. This mirrors ckBTC's `update_balance`. (The existing helper-contract scraping is unchanged.) -* **Phase 1 delegation is installed lazily, before the first sweep, and is permanent.** - For ERC-20-only crediting, delegated code on the deposit address is harmless - (ERC-20 transfers never execute recipient code), so one authorization per address — - ever — suffices. Phase 2 revisits this for native ETH (see below). +* **No up-front spending per registered address (anti-DoS).** `get_deposit_address` + is pure key derivation plus a state entry: no threshold-ECDSA signature, no + Ethereum transaction (`R13`). Registrations are free for callers, so any eager + per-address spending — signing the delegation up-front, let alone submitting it + on-chain — would let an attacker drain the minter's cycles and ETH by spamming + registrations. The pipeline is strictly gated: register → observe a balance of a + supported token ≥ the per-token minimum at the address (targeted, claim-driven + scan) → only then sign the delegation and sweep. For the same reason the balance + scanning itself must stay bounded: the registered set is attacker-inflatable, so + scans are claim-driven (`notify_deposit`, guarded per account) or bounded batches, + never an unbounded standing scan of every registered address. +* **Phase 1 delegation is therefore installed lazily, with the first sweep, and is + permanent.** For ERC-20-only crediting, delegated code on the deposit address is + harmless (ERC-20 transfers never execute recipient code), so one authorization per + address — ever — suffices. Phase 2 revisits this for native ETH (see below). * **Fees are deducted from the minted amount.** A CEX depositor owns no ckETH to pay gas with, so the sweep cost is recovered as a per-token flat fee subtracted at mint time (`minted = amount - deposit_fee`), like ckBTC's check fee. Flat and @@ -261,7 +278,10 @@ own principal is rejected). first call is an update call that registers the address in state (`deposit_addresses: Account ↔ Address` bimap + per-address bookkeeping: `registered_at_block`, delegation status, credited/swept counters), emitting a - `DepositAddressRegistered` audit event. Subsequent calls are cheap lookups. + `DepositAddressRegistered` audit event — and does nothing else: no threshold-ECDSA + signature, no Ethereum transaction (`R13`). Any per-address spending happens only + once a balance ≥ the per-token minimum has been observed at the address. + Subsequent calls are cheap lookups. ### Deposit detection and minting (Phase 1, ckERC20) @@ -345,9 +365,11 @@ is moot (fixed destination, no state). ### Sweeping task -* A periodic task selects deposit addresses with credited-but-unswept balances where +* A periodic task selects deposit addresses with observed-but-unswept balances where `unswept_value ≥ sweep_gas_cost × margin` or `age > max_age`, up to `N` addresses - per batch (gas-limit bound; initial `N ≈ 20`). + per batch (gas-limit bound; initial `N ≈ 20`). Only addresses that pass this gate + ever cost the minter anything: the delegation authorization is signed here, not at + registration time (`R13`). * One type-`0x04` transaction from the main address: * `authorization_list`: tuples for all not-yet-delegated addresses in the batch (≈ 12'500–25'000 gas each, one-time); From 0e084e6ac916b33f3e49c28d31637f5f8136e99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 15:39:10 +0000 Subject: [PATCH 09/13] docs: bulk balance observation needs EVM-RPC JSON-RPC batch support Observing balances of many registered deposit addresses (the R13 gate) must not cost one HTTPS outcall per address and provider. Record the dependency on the EVM-RPC canister eth_batch endpoint (dfinity/evm-rpc-canister#561, in progress) and the Multicall3 aggregate3 alternative usable meanwhile. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index 86f9dbbfd398..4ca226e03274 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -258,6 +258,15 @@ own principal is rejected). threshold consensus (`src/eth_rpc_client/`); every new call (`eth_getLogs` per deposit address, `eth_getBalance`, `eth_getTransactionCount` for deposit EOAs) must use the same reduction strategies. +* Each EVM-RPC call today is one HTTPS outcall *per provider* and each outcall burns + cycles, so observing the balances of many registered addresses (`R13` gate) must + not cost one call per address. This depends on **JSON-RPC batch request support in + the EVM-RPC canister** (`eth_batch`, + [dfinity/evm-rpc-canister#561](https://github.com/dfinity/evm-rpc-canister/pull/561), + in progress): one batch of `eth_getBalance` / `eth_call` requests per outcall. + Until it lands, a [Multicall3](https://www.multicall3.com/) `aggregate3` `eth_call` + can read many `balanceOf` (and, via `getEthBalance`, native ETH) values in a single + request — at the cost of depending on an extra on-chain contract. * The minter is event-sourced (`src/state/audit.rs`, `src/state/event.rs`): all new state must be reconstructible from persisted events (`R8`). * Deposits are only credited at *finalized* blocks, as today. @@ -302,6 +311,14 @@ own principal is rejected). * A frontend (e.g. OISY) polls `notify_deposit` after showing the address, so the flow is automatic from the user's perspective; a background timer may additionally re-check addresses with recent activity, bounded to a fixed batch per tick. +* Scanning many registered addresses at once (the `R13` gate, and cheap periodic + re-checks) reads balances in bulk rather than per address: a single JSON-RPC batch + of `balanceOf` `eth_call`s / `eth_getBalance`s per HTTPS outcall once the EVM-RPC + canister supports `eth_batch` + ([dfinity/evm-rpc-canister#561](https://github.com/dfinity/evm-rpc-canister/pull/561)), + or one Multicall3 `aggregate3` `eth_call` for a whole batch meanwhile. A cheap + balance scan decides *whether* to act; the log-based steps above remain the source + of truth for crediting (variant A) or the helper event does (variant B). ### Sweeper delegate contract From 9ee42878b7810f3d08553c09fcd16b3b93ef16da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 15:50:33 +0000 Subject: [PATCH 10/13] docs: Phase 2 uses separate, never-delegated ETH deposit addresses A native-ETH deposit carries its own gas, so a dedicated ETH deposit address (second derivation schema tag) never needs EIP-7702 at all: the minter sweeps it with a plain 21k-gas transfer signed by the address' own derived key, paying gas from the swept balance. The address never carries code, so fixed-21000-gas CEX withdrawals always succeed and R12 holds trivially. The set-and-clear delegation lifecycle on a shared address is demoted to a fallback. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 59 +++++++++++++++------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index 4ca226e03274..4ad5a9218d54 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -123,9 +123,10 @@ The design is delivered in two phases: * A CEX that batches ETH withdrawals through a contract (internal transactions) provides no sender information without trace APIs; Phase 2 compliance screening is therefore weaker for ETH than for ERC-20 (see Phase 2 section). - * During the short window in which a deposit address carries delegated code, a - fixed-21'000-gas ETH transfer to it fails on the sender side (`R12` guarantees no - loss). + * An account's ETH deposit address (Phase 2) differs from its ERC-20 one, matching + the per-asset deposit-address UX of exchanges; sending the wrong asset class to + an address is not credited automatically, but funds always remain recoverable + (key-controlled addresses, `R12` guarantees no loss on the sender side). ## Design Decisions @@ -181,7 +182,8 @@ The design is delivered in two phases: * **Phase 1 delegation is therefore installed lazily, with the first sweep, and is permanent.** For ERC-20-only crediting, delegated code on the deposit address is harmless (ERC-20 transfers never execute recipient code), so one authorization per - address — ever — suffices. Phase 2 revisits this for native ETH (see below). + address — ever — suffices. Phase 2 sidesteps the question for native ETH with + separate, never-delegated ETH deposit addresses (see below). * **Fees are deducted from the minted amount.** A CEX depositor owns no ckETH to pay gas with, so the sweep cost is recovered as a per-token flat fee subtracted at mint time (`minted = amount - deposit_fee`), like ckBTC's check fee. Flat and @@ -419,21 +421,40 @@ Two ETH-specific problems and their resolutions: compliance before Phase 2 ships. 2. **Fixed 21'000-gas transfers vs delegated code (`R12`).** A value transfer to an address with EIP-7702 delegated code *executes* that code, and a transfer with - exactly 21'000 gas has zero gas left for execution — it always fails. Exchanges - commonly hard-code 21'000 for ETH withdrawals. Phase 2 therefore switches the - delegation lifecycle for accounts using ETH deposits from *permanent* to - *set-and-clear*: - * Sweep batch transaction 1: authorizations installing the delegate + Multicall3 - `sweepEth()`/`sweepErc20()` calls; - * Sweep batch transaction 2: authorizations delegating to `address(0)`, which - clears the code (per EIP-7702), restoring a plain EOA. - * Cost: two tECDSA signatures and ≈ 2 × 12'500–25'000 gas per address per sweep - cycle, instead of one signature ever. Outside the short set→clear window, the - address is codeless and fixed-gas transfers succeed; inside it they fail on the - sender side without loss (`R12`). - * Whether Phase 1 addresses also move to set-and-clear (uniform policy) or keep - permanent delegation (cheaper) is decided at Phase 2 based on measured CEX - behavior for ETH withdrawals. + exactly 21'000 gas has zero gas left for execution — it always fails (with the + Phase 1 delegate, which deliberately has no `receive()`, it fails at any gas). + Exchanges commonly hard-code 21'000 for ETH withdrawals, so ETH deposits and a + (permanently) delegated address are incompatible. + + Resolution: **a separate, ETH-specific deposit address per account, which is + never delegated.** The key observation is that a native-ETH deposit *carries its + own gas* — the EIP-7702 machinery exists only because ERC-20 tokens cannot pay + for their own movement, and ETH can: + * Derivation reuses the Phase 1 scheme with a second schema tag + (`SCHEMA_ETH_DEPOSIT_ADDRESS = [2u8]` instead of `[1u8]`), so an account's ETH + deposit address differs from its ERC-20 one. + * The ETH address never carries code: fixed-21'000-gas CEX withdrawals *always* + succeed — `R12` is satisfied trivially, with no failure window at all. + * Sweep: a plain EIP-1559 transfer of `balance - fee` signed with the address' + own derived key (`sign_with_ecdsa`), gas paid from the swept balance itself — + 21'000 gas, the cheapest possible sweep; the ETH minimum deposit covers it. + One tECDSA signature and a per-address nonce (only the minter ever sends from + it) per sweep; fee-refund dust left at the address rolls into the next sweep. + * Under variant B, the sweep is instead a call to the helper's + `depositEth{value: balance - fee}(principal, subaccount)` signed the same way: + slightly more gas, but the sweep emits the canonical event and the existing + pipeline credits ckETH unchanged. + * Cross-asset mistakes remain recoverable in both directions: ERC-20 sent to an + ETH address sits at a tECDSA-controlled EOA (delegate it on demand to sweep); + ETH sent to a not-yet-delegated ERC-20 address is recovered by the delegate's + `sweepEth`, and once the ERC-20 address is delegated, plain ETH sends to it + fail on the sender side, so funds never leave the exchange. + + The previously considered *set-and-clear* delegation lifecycle on a single shared + address (install the delegate, sweep, re-delegate to `address(0)` — two tECDSA + signatures and ≈ 2 × 12'500–25'000 gas per sweep cycle, with a short window in + which fixed-gas ETH transfers fail) is kept only as a fallback if per-asset + addresses are rejected for UX reasons. ### Test plan From c4141183ba15929db3dad7bfa31367a140e707d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 16:49:38 +0000 Subject: [PATCH 11/13] docs: sweep gas is burned from the ckETH fee account, never from backing Sweep transactions are paid in ETH by the minter, but that ETH backs ckETH 1:1. New requirement R14: burn-first from the minter's fee account on the ckETH ledger, at least the transaction's maximum fee; track burned-but-unspent as prepaid credit for subsequent burns, never re-mint. Deposit fees are minted to a per-token fee account (full deposited amount minted, supply stays equal to backing); converting that per-token revenue into ckETH to replenish the fee account is a treasury operation out of scope. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 41 ++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index 4ad5a9218d54..e2a23c207bba 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -89,6 +89,12 @@ The design is delivered in two phases: supported token of at least the per-token minimum deposit amount. (Registrations are free for callers; anything the minter spends per registration is a DoS vector on its cycles and ETH.) +* `R14`: Sweeping never reduces the 1:1 backing of ckETH. Before any ETH is spent on + a sweep transaction, the minter burns from its fee account on the ckETH ledger at + least the maximum fee of that transaction; at all times, cumulative ckETH burned + for sweeping ≥ cumulative ETH spent on sweeping. Burned-but-unspent amounts are + tracked and offset against subsequent burns; they are never re-minted. If the fee + account cannot cover a sweep, no sweep is submitted. ### Phase 2 (ckETH) @@ -119,6 +125,11 @@ The design is delivered in two phases: this invisible in practice. * **Automatic recovery of unsupported-token deposits**: funds remain recoverable (key-controlled address) but recovery tooling is future work. +* **Replenishing the sweep-gas fee account**: sweep gas is burned from the minter's + ckETH fee account (`R14`), while deposit-fee revenue accrues per ckToken + (ckUSDC, ckUSDT, …). Converting that revenue into ckETH to keep the fee account + funded is a treasury/market operation outside this design; the design only + requires that sweeping halts safely when the fee account is empty. * Accepted residual limitations: * A CEX that batches ETH withdrawals through a contract (internal transactions) provides no sender information without trace APIs; Phase 2 compliance screening is @@ -184,11 +195,28 @@ The design is delivered in two phases: harmless (ERC-20 transfers never execute recipient code), so one authorization per address — ever — suffices. Phase 2 sidesteps the question for native ETH with separate, never-delegated ETH deposit addresses (see below). -* **Fees are deducted from the minted amount.** A CEX depositor owns no ckETH to pay - gas with, so the sweep cost is recovered as a per-token flat fee subtracted at mint - time (`minted = amount - deposit_fee`), like ckBTC's check fee. Flat and +* **Fees are deducted from the minted amount and land in a fee account.** A CEX + depositor owns no ckETH to pay gas with, so the sweep cost is recovered as a + per-token flat fee: the depositor is credited `amount - deposit_fee` and the fee + portion is minted to a minter-controlled fee account on the same ckToken ledger — + the full deposited amount is swept, so minting it in full keeps supply exactly + equal to backing and makes fee revenue explicit and auditable. Flat and proposal-configurable rather than oracle-priced, for simplicity and predictability (`R7`). +* **Sweep gas is paid by the minter's ETH, but never out of ckETH backing (`R14`).** + The ETH at the minter's main address backs ckETH 1:1, so spending it on sweep gas + without a matching ckETH burn would leave ckETH under-backed. Sweeps are therefore + funded exclusively through the minter's fee account on the ckETH ledger, mirroring + how withdrawals already pay for gas (burn ckETH, spend ETH): before submitting a + sweep, the minter **burns first** — at least the transaction's maximum fee + (`gas_limit × max_fee_per_gas`, an overestimate by construction) minus any + previously burned-but-unspent credit. The receipt's effective fee is then deducted + from a running `prepaid_sweep_gas` counter; the leftover of the overestimate is + **not re-minted** but carried as credit for the next burn, so the invariant + "cumulative burned ≥ cumulative spent" holds at every instant. An empty ckETH fee + account halts sweeping (safely: under variant A, credited balances are unaffected + and deposits keep accumulating at key-controlled addresses; under variant B, + crediting pauses with sweeping); replenishing it is out of scope (see Non-goals). ### Open decision: what the sweep does — variant A (direct) vs variant B (through the helper contract) @@ -389,6 +417,13 @@ is moot (fixed destination, no state). per batch (gas-limit bound; initial `N ≈ 20`). Only addresses that pass this gate ever cost the minter anything: the delegation authorization is signed here, not at registration time (`R13`). +* Before submission, burn `max(0, gas_limit × max_fee_per_gas - prepaid_sweep_gas)` + from the minter's fee account on the ckETH ledger and add it to + `prepaid_sweep_gas`; abort the sweep (and retry later) if the burn fails (`R14`). + On the receipt, subtract the effective transaction fee from `prepaid_sweep_gas` — + the surplus of the overestimate stays as credit for the next burn and is never + re-minted. Both movements are recorded as audit events and `prepaid_sweep_gas` is + exposed on the dashboard (`R8`, `R9`). * One type-`0x04` transaction from the main address: * `authorization_list`: tuples for all not-yet-delegated addresses in the batch (≈ 12'500–25'000 gas each, one-time); From 97e9e67ea4232d1f40255eb63de463d7ff3e9415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 16:57:27 +0000 Subject: [PATCH 12/13] docs: end-to-end sequence diagrams for ckUSDT and ckETH deposits Mermaid sequence diagrams for the full flow: ckUSDT under variant A (direct sweep, mint on finalized deposit) and variant B (sweep through the helper, existing pipeline mints), and ckETH in Phase 2 (dedicated never-delegated address, deposit pays its own sweep gas). Diagramming surfaced a gap now closed: ETH sweeps need no R14 burn, but under variant A the sweep's max fee must be capped by the charged deposit fee since crediting happens before the sweep. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 96 ++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index e2a23c207bba..ae182d2821ea 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -274,6 +274,96 @@ own principal is rejected). ## Implementation +### End-to-end flows + +Deposit of USDT from a CEX under **variant A** (direct sweep; crediting via a new +detection path, mint on finalized deposit, sweep asynchronously): + +```mermaid +sequenceDiagram + autonumber + actor User as User (principal p) + participant CEX + participant Minter + participant Eth as Ethereum (via EVM-RPC) + participant CkUsdtLedger as ckUSDT ledger + participant CkEthLedger as ckETH ledger + + User->>Minter: get_deposit_address(p) + Note right of Minter: derive addr(p) locally, register it.
No tECDSA signature, no Ethereum tx (R13) + Minter-->>User: addr(p) + User->>CEX: withdraw USDT to addr(p) + CEX->>Eth: USDT.transfer(addr(p), 250) + loop frontend polls until credited + User->>Minter: notify_deposit(p) + Minter->>Eth: eth_getLogs(USDT Transfer to addr(p), up to finalized) + end + Note over Minter: amount >= min (R4), sender not blocked (R3),
dedup by (tx hash, log index) (R2) + Minter->>CkUsdtLedger: mint 250 - fee to p + Minter->>CkUsdtLedger: mint fee to minter fee account + Note over Minter: user is credited; sweeping is asynchronous
treasury consolidation (R5) + Minter->>CkEthLedger: burn max_tx_fee - prepaid_sweep_gas
from the ckETH fee account (R14) + Note over Minter: sign EIP-7702 authorization for addr(p)
(tECDSA; only before the first sweep) + Minter->>Eth: type-0x04 tx: sweepErc20 on addr(p)
USDT: addr(p) -> minter address + Eth-->>Minter: receipt: prepaid_sweep_gas -= effective fee +``` + +Deposit of USDT from a CEX under **variant B** (sweep through the existing helper +contract; crediting via the unchanged existing pipeline): + +```mermaid +sequenceDiagram + autonumber + actor User as User (principal p) + participant CEX + participant Minter + participant Eth as Ethereum (via EVM-RPC) + participant CkUsdtLedger as ckUSDT ledger + participant CkEthLedger as ckETH ledger + + User->>Minter: get_deposit_address(p) + Minter-->>User: addr(p) + User->>CEX: withdraw USDT to addr(p) + CEX->>Eth: USDT.transfer(addr(p), 250) + Minter->>Eth: balance scan of registered addresses (latest block) + Note over Minter: scheduling hint only, no finality needed:
a reorged deposit just wastes the sweep's gas + Minter->>CkEthLedger: burn max_tx_fee - prepaid_sweep_gas (R14) + Minter->>Eth: type-0x04 tx: sweepErc20(tokens, p, s) on addr(p)
= approve + helper.depositErc20:
USDT: addr(p) -> minter address, and the helper emits
ReceivedEthOrErc20(USDT, addr(p), 250, p, s) + Eth-->>Minter: receipt: prepaid_sweep_gas -= effective fee + Note over Minter,Eth: from here the EXISTING deposit pipeline runs unchanged + Minter->>Eth: eth_getLogs(helper contract, up to finalized) + Note over Minter: cross-check owner addr(p) vs p against own map,
dedup by (tx hash, log index) + Minter->>CkUsdtLedger: mint 250 - fee to p, fee to minter fee account +``` + +Deposit of ETH from a CEX (**Phase 2**, dedicated never-delegated ETH deposit +address; the deposit pays its own sweep gas, no `R14` burn involved): + +```mermaid +sequenceDiagram + autonumber + actor User as User (principal p) + participant CEX + participant Minter + participant Eth as Ethereum (via EVM-RPC) + participant CkEthLedger as ckETH ledger + + User->>Minter: get_deposit_address(p) for ETH + Minter-->>User: eth_addr(p) (schema 2: never delegated, never any code) + User->>CEX: withdraw ETH to eth_addr(p) + CEX->>Eth: plain transfer, even with a fixed 21000 gas limit (R12) + User->>Minter: notify_deposit(p) (frontend polls) + Minter->>Eth: eth_getBalance(eth_addr(p), finalized) + alt variant A + Minter->>CkEthLedger: mint balance delta - fee to p (R11),
fee to minter fee account + Minter->>Eth: EIP-1559 tx FROM eth_addr(p), signed via tECDSA:
transfer balance - gas to the minter address
(max fee capped by the charged deposit fee) + else variant B + Minter->>Eth: EIP-1559 tx FROM eth_addr(p), signed via tECDSA:
helper.depositEth{value: balance - gas}(p, s) + Minter->>Eth: eth_getLogs(helper contract, up to finalized) + Minter->>CkEthLedger: mint to p (existing pipeline, unchanged) + end +``` + ### Constraints * The minter's transaction layer supports only EIP-1559 (type `0x02`) transactions @@ -475,6 +565,12 @@ Two ETH-specific problems and their resolutions: 21'000 gas, the cheapest possible sweep; the ETH minimum deposit covers it. One tECDSA signature and a per-address nonce (only the minter ever sends from it) per sweep; fee-refund dust left at the address rolls into the next sweep. + * No `R14` burn is involved: the gas never comes out of the minter's + main-address backing. However, since (under variant A) the account is credited + `balance - fee` *before* the sweep executes, the sweep's + `gas_limit × max_fee_per_gas` must be capped by the charged deposit fee — if + gas prices exceed that cap, the sweep waits — so that the gas spent can never + exceed what was withheld from minting. * Under variant B, the sweep is instead a call to the helper's `depositEth{value: balance - fee}(principal, subaccount)` signed the same way: slightly more gas, but the sweep emits the canonical event and the existing From bb4a9e13a9b4bb7c59aafb8ce13f23ecd89b975b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Demay?= Date: Fri, 3 Jul 2026 17:01:53 +0000 Subject: [PATCH 13/13] docs: fix mermaid parse error (semicolons in note text) Mermaid treats ';' as a statement separator, so semicolons inside note text split the note into an invalid statement. All three diagrams now validated with mermaid-cli. Co-Authored-By: Claude Fable 5 --- rs/ethereum/cketh/docs/deposit_from_cex.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/ethereum/cketh/docs/deposit_from_cex.md b/rs/ethereum/cketh/docs/deposit_from_cex.md index ae182d2821ea..4e05607e9fbd 100644 --- a/rs/ethereum/cketh/docs/deposit_from_cex.md +++ b/rs/ethereum/cketh/docs/deposit_from_cex.md @@ -301,9 +301,9 @@ sequenceDiagram Note over Minter: amount >= min (R4), sender not blocked (R3),
dedup by (tx hash, log index) (R2) Minter->>CkUsdtLedger: mint 250 - fee to p Minter->>CkUsdtLedger: mint fee to minter fee account - Note over Minter: user is credited; sweeping is asynchronous
treasury consolidation (R5) + Note over Minter: user is credited - sweeping is asynchronous
treasury consolidation (R5) Minter->>CkEthLedger: burn max_tx_fee - prepaid_sweep_gas
from the ckETH fee account (R14) - Note over Minter: sign EIP-7702 authorization for addr(p)
(tECDSA; only before the first sweep) + Note over Minter: sign EIP-7702 authorization for addr(p)
(tECDSA, only before the first sweep) Minter->>Eth: type-0x04 tx: sweepErc20 on addr(p)
USDT: addr(p) -> minter address Eth-->>Minter: receipt: prepaid_sweep_gas -= effective fee ```