Skip to content

feat(cketh): give the sweeper address its own transaction pipeline - #11144

Open
gregorydemay wants to merge 8 commits into
greg/cketh-lane-genericfrom
greg/sweeper-send-lane
Open

feat(cketh): give the sweeper address its own transaction pipeline#11144
gregorydemay wants to merge 8 commits into
greg/cketh-lane-genericfrom
greg/sweeper-send-lane

Conversation

@gregorydemay

@gregorydemay gregorydemay commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Why

The minter's dedicated sweeper address needs to send Ethereum transactions, and it must not share the main address' nonce sequence: a sweep stuck behind a fee-starved transaction would head-of-line-block every user withdrawal.

What

A second instance of the transaction pipeline, for the sweeper address, on a nonce sequence of its own. A sweep burns no ckETH, so it is keyed by a plain counter rather than a ledger burn index, and is never reimbursed; it pays gas from the sweeper's prepaid balance, so it has no transaction fee it can fail to cover. Five audit events record the pipeline's transitions, with reconstruction, Candid mirrors and .did.

The sweeper's start nonce can be set from both lifecycle arguments. It is optional on install where the main address' equivalent is required, because install arguments are replayed from the event log and a required field would be missing from every event already written.

Scope

The pipeline only. No timer drives it and nothing enqueues a sweep, so the new state stays empty: the sending task is DEFI-2926 in #11237, and EIP-7702 first-time delegation is #11250, stacked above. The sweep-queue source and prepaid-gas gating are still to come.

Sweeper funding — burning ckETH from the minter's fee subaccount to prepay that gas — is a separate stack (DEFI-2933) that meets this one only in the withdrawal pipeline, where its request variant has already landed. Hence the audit events here are numbered from n28, above the funding event master now owns.

Candid compatibility

CI_OVERRIDE_DIDC_CHECK is set. The check flags the five cases this PR adds to the Event payload variant, since a variant returned to callers may not grow under Candid subtyping. It is the additive shape every new audit event has taken: existing cases keep their names and fields, and callers matching exhaustively on the old set see the new ones only for sweeper activity, which nothing enqueues yet. The new optional nonce field on the two argument types is compatible on its own.

Stack created with GitHub Stacks CLIGive Feedback 💬

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prepares ckETH minter support for Ethereum transactions sent from the dedicated sweeper address by introducing a second, independent transaction “send lane” with its own nonce sequence, ensuring sweeps cannot head-of-line block withdrawals from the main minter address.

Changes:

  • Generalizes the existing EthTransactions state machine into a reusable TransactionLane<R> and keeps main-lane behavior via a type alias.
  • Introduces SweepId/SweepRequest, a dedicated State::sweeper_transactions lane, and a new timer-driven process_sweeper_transactions pipeline that signs with derivation path [3].
  • Extends audit/event plumbing (events n27..n31) and Candid surface area (DID + endpoint payload mirrors) for sweeper-lane observability and replay.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
rs/ethereum/cketh/minter/tests/dump_stable_memory.rs Extends stable-memory dump test mapping to cover new sweeper-lane audit event payloads.
rs/ethereum/cketh/minter/src/withdraw.rs Refactors RPC helpers to be sender-agnostic (reused by both lanes) and threads sender into count/finalization.
rs/ethereum/cketh/minter/src/sweep.rs Adds the sweeper transaction processing task (create/sign/send/resubmit/finalize) for the sweeper lane.
rs/ethereum/cketh/minter/src/state/transactions/tests.rs Updates expectations for generalized lane terminology and adds dedicated sweep-lane unit tests.
rs/ethereum/cketh/minter/src/state/transactions/mod.rs Introduces TransactionLane<R>, adds SweepId/SweepRequest, and centralizes lane-generic mechanics + main-lane-only reimbursement/status logic.
rs/ethereum/cketh/minter/src/state/tests.rs Updates state fixtures/equivalence tests to include new sweeper-lane state and upgrade arg field.
rs/ethereum/cketh/minter/src/state/event.rs Adds new EventType variants for sweep request lifecycle and sweeper transaction lifecycle.
rs/ethereum/cketh/minter/src/state/audit/tests.rs Extends audit replay test mapping for sweeper-lane events.
rs/ethereum/cketh/minter/src/state/audit.rs Extends state-transition application logic to replay sweeper-lane events into state.
rs/ethereum/cketh/minter/src/state.rs Adds sweeper_transactions + next_sweep_id to state and supports overriding the sweeper nonce via upgrade args.
rs/ethereum/cketh/minter/src/main.rs Schedules the sweeper-lane timer task and exposes sweeper-lane events via get_events.
rs/ethereum/cketh/minter/src/lifecycle/upgrade.rs Adds next_sweeper_transaction_nonce upgrade argument.
rs/ethereum/cketh/minter/src/lifecycle/init.rs Initializes the sweeper lane at nonce 0 and initializes next_sweep_id.
rs/ethereum/cketh/minter/src/lib.rs Exposes the new sweep module.
rs/ethereum/cketh/minter/src/endpoints.rs Extends public event payload variants with sweeper-lane events.
rs/ethereum/cketh/minter/src/deposit_address/mod.rs Makes sweeper_derivation_path available to the new sweeper-lane signing code.
rs/ethereum/cketh/minter/cketh_minter.did Updates DID to include new upgrade arg and sweeper-lane event variants.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/transactions/mod.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/audit.rs Outdated
@gregorydemay gregorydemay changed the title feat(cketh): give the sweeper address its own transaction send-lane feat(cketh): give the sweeper address its own transaction send-lane [override-didc-check] Aug 14, 2026
@gregorydemay
gregorydemay force-pushed the greg/sweeper-send-lane branch from 94dc94f to a3fe1e3 Compare August 17, 2026 12:29
@gregorydemay
gregorydemay changed the base branch from mathias/DEFI-2933-hardening to master August 17, 2026 12:30
@gregorydemay
gregorydemay requested a lite review from Copilot August 17, 2026 12:30
@gregorydemay gregorydemay changed the title feat(cketh): give the sweeper address its own transaction send-lane [override-didc-check] feat(cketh): give the sweeper address its own transaction send-lane Aug 17, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (3)

rs/ethereum/cketh/minter/src/state/audit.rs:112

  • next_sweep_id is updated unconditionally from the accepted request’s id. If events are ever replayed out-of-order or a malformed event is recorded, this can move next_sweep_id backwards and allow duplicate SweepIds to be minted later. Safer to monotonically advance it (take the max of the current value and request.id + 1).
        EventType::AcceptedSweepRequest(request) => {
            state.next_sweep_id = SweepId(request.id.0.saturating_add(1));
            state
                .sweeper_transactions
                .record_withdrawal_request(request.clone());
        }

rs/ethereum/cketh/minter/src/withdraw.rs:499

  • fetch_finalized_receipts is documented to return None when the batch should be retried later, but it currently assert_eq!s that every expected id has a receipt. If the RPC temporarily returns Ok(None) for all hashes of some finalized id (RPC lag / pruning / transient issues), this will trap the canister instead of retrying.
    let actual_finalized_ids: BTreeSet<Id> = receipts.keys().copied().collect();
    assert_eq!(
        expected_finalized_ids, actual_finalized_ids,
        "ERROR: unexpected transaction receipts for some ids"
    );
    Some(receipts)

rs/ethereum/cketh/minter/src/state/transactions/mod.rs:300

  • The failure log says the sweep funds “stay at the deposit address”, but sweep transactions are sent from the dedicated sweeper address (so on failure funds remain at the sweeper address). This message is misleading for operators/debugging.
        log!(
            INFO,
            "[record_finalized_transaction]: sweep {} to {} FAILED (tx {}); sweeps are never \
             reimbursed, the funds stay at the deposit address for a later sweep",
            self.id,
            self.destination,
            receipt.transaction_hash,
        );

@gregorydemay
gregorydemay force-pushed the greg/sweeper-send-lane branch from 4983cd1 to 091aac8 Compare August 18, 2026 07:34
@gregorydemay
gregorydemay changed the base branch from master to greg/cketh-lane-generic August 18, 2026 07:35
@gregorydemay gregorydemay changed the title feat(cketh): give the sweeper address its own transaction send-lane feat(cketh): give the sweeper address its own transaction send-lane [override-didc-check] Aug 18, 2026
@gregorydemay
gregorydemay requested a lite review from Copilot August 18, 2026 07:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Comment thread rs/ethereum/cketh/minter/src/withdraw.rs Outdated
@gregorydemay gregorydemay added the CI_OVERRIDE_DIDC_CHECK Skips the backwards compatibility didc check (explain in PR description why) label Aug 18, 2026
@gregorydemay gregorydemay changed the title feat(cketh): give the sweeper address its own transaction send-lane [override-didc-check] feat(cketh): give the sweeper address its own transaction send-lane Aug 18, 2026
@gregorydemay
gregorydemay force-pushed the greg/sweeper-send-lane branch from 091aac8 to ef55c9e Compare August 18, 2026 09:10
@gregorydemay
gregorydemay force-pushed the greg/sweeper-send-lane branch from ef55c9e to 9e051b7 Compare August 18, 2026 09:24
@gregorydemay
gregorydemay requested a lite review from Copilot August 18, 2026 09:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

rs/ethereum/cketh/minter/src/state/transactions/mod.rs:482

  • The LaneRequest doc comment says the trait also covers reimbursement behavior, but the trait itself doesn’t define any reimbursement-related API; reimbursement is handled elsewhere (e.g., on EthTransactions / WithdrawalRequest). This is misleading now that SweepRequest also implements LaneRequest.
/// A request that can flow through a [`TransactionLane`]: it carries an identity used as the
/// lane's alternate map key, knows the EIP-1559 transaction it turns into, and (for lanes whose
/// failed transactions are paid back) how to reimburse a failure.
///
/// Implemented by [`WithdrawalRequest`] — the minter **main address** lane (`Id = LedgerBurnIndex`)

rs/ethereum/cketh/minter/src/state/transactions/mod.rs:247

  • max_transaction_fee is only used by resubmission_strategy; to_transaction doesn’t consult it when setting max_fee_per_gas, so describing it as a general “ceiling on the transaction fee” is inaccurate. Either enforce the cap in to_transaction or adjust the field documentation to match the current behavior.
    /// Ceiling on the transaction fee, used as the resubmission fee cap.

@gregorydemay
gregorydemay force-pushed the greg/sweeper-send-lane branch from 9e051b7 to 705846a Compare August 18, 2026 11:39
@gregorydemay gregorydemay changed the title feat(cketh): give the sweeper address its own transaction send-lane feat(cketh): give the sweeper address its own transaction pipeline Aug 18, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (2)

rs/ethereum/cketh/minter/src/state/tests.rs:689

  • arb_upgrade_arg always sets next_sweeper_transaction_nonce to None, so proptests that generate EventType::Upgrade never exercise (de)serialization and upgrade handling for the new field.
prop_compose! {
    fn arb_upgrade_arg()(
        contract_address in proptest::option::of(arb_address()),
        ethereum_block_height in proptest::option::of(arb_block_tag()),
        minimum_withdrawal_amount in proptest::option::of(arb_nat()),
        next_transaction_nonce in proptest::option::of(arb_nat()),
        ledger_suite_orchestrator_id in proptest::option::of(arb_principal()),
        erc20_helper_contract_address in proptest::option::of(arb_address()),
        last_erc20_scraped_block_number in proptest::option::of(arb_nat()),
        evm_rpc_id in proptest::option::of(arb_principal()),
        deposit_with_subaccount_helper_contract_address in proptest::option::of(arb_address()),
        last_deposit_with_subaccount_scraped_block_number in proptest::option::of(arb_nat()),
        sweeper_contract_address in proptest::option::of(arb_address()),
    ) -> UpgradeArg {
        UpgradeArg {
            next_sweeper_transaction_nonce: None,
            ethereum_contract_address: contract_address.map(|addr| addr.to_string()),
            ethereum_block_height,
            minimum_withdrawal_amount,
            next_transaction_nonce,
            ledger_suite_orchestrator_id,
            erc20_helper_contract_address: erc20_helper_contract_address.map(|addr| addr.to_string()),
            last_erc20_scraped_block_number,
            evm_rpc_id,
            deposit_with_subaccount_helper_contract_address: deposit_with_subaccount_helper_contract_address.map(|addr| addr.to_string()),
            last_deposit_with_subaccount_scraped_block_number,
            ethereum_sweeper_contract_address: sweeper_contract_address.map(|addr| addr.to_string()),
        }

rs/ethereum/cketh/minter/src/state/transactions/request.rs:225

  • SweepRequest::assert_created_transaction validates destination and amount, but not the request calldata. For sweeps the data field can be semantically critical (e.g. ERC-20 sweep calls), so a mismatched CreatedSweeperTransaction event could slip through replay without being detected.
    fn assert_created_transaction(&self, transaction: &Eip1559TransactionRequest) {
        assert_eq!(
            self.destination, transaction.destination,
            "BUG: request and transaction destination mismatch"
        );
        assert_eq!(
            transaction.amount, self.amount,
            "BUG: sweep transaction amount should equal the request amount"
        );
    }

@gregorydemay
gregorydemay requested a lite review from Copilot August 20, 2026 15:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.

Suppressed comments (2)

rs/ethereum/cketh/minter/src/state/audit/tests.rs:251

  • map_signed_sweep_transaction reconstructs signed sweeper transactions as SweepTransaction::Eip1559(..) unconditionally, so the audit replay test harness cannot represent SignedSweeperTransaction events for EIP-7702 (type 0x04) delegating sweeps. This will break replay once sweeper sending is enabled and such events are emitted.
        fn map_signed_sweep_transaction(raw_transaction: &str) -> SignedSweepTransaction {
            let (transaction, signature) = decode_signed_transaction(raw_transaction);
            SignedSweepTransaction::from((SweepTransaction::Eip1559(transaction), signature))
        }

rs/ethereum/cketh/minter/tests/dump_stable_memory.rs:194

  • map_signed_sweep_transaction always reconstructs a SignedSweepTransaction as SweepTransaction::Eip1559(..). That makes this stable-memory dump tool unable to reconstruct SignedSweeperTransaction events for delegating sweeps (type 0x04 / EIP-7702), even though the sweeper lane supports signing EIP-7702 transactions.
fn map_signed_sweep_transaction(raw_transaction: &str) -> SignedSweepTransaction {
    let (transaction, signature) = decode_signed_transaction(raw_transaction);
    SignedSweepTransaction::from((SweepTransaction::Eip1559(transaction), signature))
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (2)

rs/ethereum/cketh/minter/src/state/transactions/request.rs:249

  • SweepRequest::create_transaction computes max_fee_per_gas/max_priority_fee_per_gas purely from gas_fee_estimate.to_price(gas_limit) and does not enforce self.max_transaction_fee as a ceiling. Since resubmission_strategy() uses max_transaction_fee as the fee cap, the initial transaction can be created (and potentially sent) with a max fee that already exceeds that cap, making the ceiling ineffective. Consider deriving max_fee_per_gas from self.max_transaction_fee.into_wei_per_gas(gas_limit) (and capping max_priority_fee_per_gas accordingly), or otherwise ensuring the initial fee cannot exceed max_transaction_fee.
        Ok(Eip1559TransactionRequest {
            chain_id: ethereum_network.chain_id(),
            nonce,
            max_priority_fee_per_gas: transaction_price.max_priority_fee_per_gas,
            max_fee_per_gas: transaction_price.max_fee_per_gas,

rs/ethereum/cketh/minter/src/state/tests.rs:679

  • arb_upgrade_arg hardcodes next_sweeper_transaction_nonce: None, so the property-based event/replay tests never exercise upgrading the sweeper lane nonce (including its validation and its independence from the main lane). Since this field is now part of UpgradeArg and State::upgrade applies it, it should be generated (e.g. proptest::option::of(arb_nat())) and threaded into the returned UpgradeArg to get coverage for the new behavior.
    ) -> UpgradeArg {
        UpgradeArg {
            next_sweeper_transaction_nonce: None,
            ethereum_contract_address: contract_address.map(|addr| addr.to_string()),
            ethereum_block_height,

Comment thread rs/ethereum/cketh/minter/src/lifecycle/upgrade.rs Outdated
Comment thread rs/ethereum/cketh/minter/src/state/audit/tests.rs Outdated
Comment thread rs/ethereum/cketh/minter/BUILD.bazel Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (1)

rs/ethereum/cketh/minter/src/state/event.rs:199

  • In EventType, existing struct-like variants consistently number their fields starting at #[n(0)] (e.g. CreatedTransaction uses its first field at n(0)), but the new sweeper variants start at #[n(1)]/#[n(2)]. minicbor allows sparse tags, but this inconsistency makes the encoding scheme harder to audit/maintain. Consider renumbering these new variants to start at n(0)/n(1) for consistency (and apply the same change to SignedSweeperTransaction, ReplacedSweeperTransaction, and FinalizedSweeperTransaction below).
        #[n(1)]
        sweep_id: SweepId,
        #[n(2)]
        transaction: Eip1559TransactionRequest,
    },

@gregorydemay
gregorydemay marked this pull request as ready for review August 21, 2026 09:01
@gregorydemay
gregorydemay requested a review from a team as a code owner August 21, 2026 09:01
@github-actions github-actions Bot added the @defi label Aug 21, 2026
@zeropath-ai

zeropath-ai Bot commented Aug 21, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 30c178a.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► rs/ethereum/cketh/minter/did
Enhancement ► rs/ethereum/cketh/minter/src/dashboard/tests.rs
    Add next_sweeper_transaction_nonce initialization
Enhancement ► rs/ethereum/cketh/minter/src/endpoints.rs
    Add AcceptedSweepRequest and related sweep events to events enum
Enhancement ► rs/ethereum/cketh/minter/src/lifecycle/init.rs
    Initialize next_sweeper_transaction_nonce and wire into state
Enhancement ► rs/ethereum/cketh/minter/src/lifecycle/tests.rs
    Tests for sweeper nonce behavior and independence from main nonce
Enhancement ► rs/ethereum/cketh/minter/src/lifecycle/upgrade.rs
    UpgradeArg includes next_sweeper_transaction_nonce
Enhancement ► rs/ethereum/cketh/minter/src/main.rs
    Handle new sweep-related events in get_events
Enhancement ► rs/ethereum/cketh/minter/src/state.rs
    Add sweeper_transactions pipeline and next_sweep_id to State
Enhancement ► rs/ethereum/cketh/minter/src/state/audit.rs
    Audit/state transition support for sweep events
Enhancement ► rs/ethereum/cketh/minter/src/state/event.rs
    Define SweepId and SweepRequest event types integration
Enhancement ► rs/ethereum/cketh/minter/src/state/transactions/mod.rs
    Introduce SweepId, SweepRequest, SweeperTransactionPipeline aliases
Enhancement ► rs/ethereum/cketh/minter/src/state/transactions/request.rs
    Implement PipelineRequest for SweepRequest
Enhancement ► rs/ethereum/cketh/minter/src/state/transactions/tests.rs
    Sweep lane tests and coverage for sweeper pipeline
Enhancement ► rs/ethereum/cketh/minter/src/test_fixtures.rs
    Include next_sweeper_transaction_nonce in test fixtures
Enhancement ► rs/tests/cross_chain/ic_xc_cketh_test.rs
    Add next_sweeper_transaction_nonce to minter init args
Enhancement ► rs/ethereum/cketh/minter/tests/dump_stable_memory.rs
    Support new sweep-related event payload mappings in test dump

gregorydemay and others added 7 commits August 21, 2026 11:05
The minter's dedicated sweeper address needs to send Ethereum transactions, and
it must not share the main address' nonce sequence: a sweep stuck behind a
fee-starved transaction would head-of-line-block every user withdrawal.

Instantiate the transaction pipeline a second time, for the sweeper.
`SweepRequest` implements `PipelineRequest` with a plain counter for identity —
a sweep burns no ckETH, so there is no ledger burn index to key it by — and
`State::sweeper_transactions` drives it on a nonce sequence of its own, seeded
at 0 and overridable through the new `next_sweeper_transaction_nonce` upgrade
argument.

`SweepRequest::Error` is `Infallible`: a sweep pays its gas from the sweeper's
prepaid balance rather than out of the amount moved, so it has no fee to fail to
cover — and `CreateTransactionError`, keyed by a ckETH burn index, is a value it
could not construct anyway. Creating a sweep transaction therefore needs no error
arm at all, rather than an unreachable one.

Five audit events (`n28`..`n32`) record the sweeper pipeline's transitions, with
reconstruction, Candid `EventPayload` mirrors and `.did`.

This is the pipeline only: no timer drives it and nothing enqueues a sweep, so
the new state stays empty until the sending task lands under DEFI-2926.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`next_sweeper_transaction_nonce` could only be set on upgrade, so a fresh
install always started the sweeper pipeline at nonce 0. That is right for the
production minter, whose sweeper address is freshly derived, but wrong for a
developer reinstalling a minter against an address that has already sent
transactions: the pipeline would resubmit nonces the chain has consumed.

Accept the same value in `InitArg`, mirroring `next_transaction_nonce`.

It is optional where the main address' is required, because `InitArg` is
replayed from the event log: a required field would be absent from every Init
event already written, and the minter would trap decoding it on the next
upgrade. Absent means 0, so a fresh install is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`SweepRequest::assert_created_transaction` checked the destination and the
amount but not the call data, which for a sweep is the delegate call that does
the actual work: a `CreatedSweeperTransaction` event whose transaction moves the
right amount to the right address while calling something else would have
replayed without complaint.

Safe to tighten now rather than later, because tightening an assertion reachable
from `apply_state_transition` is only safe while no event it applies to exists:
nothing enqueues a sweep, so no minter version has ever written one of these
events.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`arb_upgrade_arg` hardcoded `next_sweeper_transaction_nonce: None`, so
`event_encoding_roundtrip` never encoded an `Upgrade` event carrying the field —
the one test that would catch a mistake in the CBOR shape of the event log,
which is exactly where a mistake cannot be undone. `arb_init_arg` generates its
equivalent; this makes the upgrade fixture match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`next_sweeper_transaction_nonce` was given CBOR index 12 in `UpgradeArg`, whose
previous field is 10, leaving an unexplained gap at 11. The 11 belongs to
`InitArg`, whose own equivalent field it is — the two structs have separate
index spaces, and this one continued the wrong count.

Free to renumber only because no minter version has written an `Upgrade` event
carrying the field yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both event round-trip mappers spelled the Candid-to-minter `TransactionReceipt`
conversion out twice, once for `FinalizedTransaction` and once for
`FinalizedSweeperTransaction` — fourteen identical lines each, in files whose
whole purpose is to prove the two representations agree.

`map_transaction_receipt` joins `map_nat` and `map_unsigned_transaction` as one
more named conversion, and the arms shrink to a call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`AcceptedSweepRequest.data` was a hex-encoded `text`, while
`UnsignedTransaction.data` — the other call data in the same `Event` variant —
is a `blob`. Two spellings of the same thing, and the odd one out was the new
one.

As a `blob` the field needs no encoding on the way out and no decoding on the
way back, which is what the `hex` dependency of the `dump_stable_memory` tool
was for; the tool builds without it now. The event is introduced in this PR, so
nothing has ever read the hex form.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gregorydemay
gregorydemay force-pushed the greg/sweeper-send-lane branch from de2eb00 to 6fbf7c1 Compare August 21, 2026 09:13
`destination` was documented as the delegated deposit address, which is the
shape the design did not take: a sweep batches, and a transaction has one `to`,
so it goes to the sweeper contract, whose batch entry point loops into every
delegated deposit address the call data names. `data` had the same problem, one
level down — it is that batch call, not a single-address `sweepErc20`, and never
a plain transfer.

Neither is the deposit helper: that one is called from inside the delegate code
running in a deposit address' context, never sent a transaction of its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI_OVERRIDE_DIDC_CHECK Skips the backwards compatibility didc check (explain in PR description why) @defi feat

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants