Skip to content

feat(relayer): add deposit recovery endpoint#458

Closed
danwt wants to merge 559 commits intomainfrom
danwt/claude/kaspa-deposit-recovery
Closed

feat(relayer): add deposit recovery endpoint#458
danwt wants to merge 559 commits intomainfrom
danwt/claude/kaspa-deposit-recovery

Conversation

@danwt
Copy link
Copy Markdown

@danwt danwt commented Jan 27, 2026

Summary

  • Add POST /kaspa/deposit/recover endpoint to manually recover Kaspa deposits that fell outside the normal lookback window
  • Useful when relayer DB has been wiped or for deposits that were missed for other reasons
  • Fetches transaction from Kaspa REST API, validates it's an escrow transfer, and queues it for processing

Usage

curl -X POST https://kaspa-relayer.mzonder.com/kaspa/deposit/recover \
  -H "Content-Type: application/json" \
  -d '{"kaspa_tx": "242b5987..."}'

Test plan

  • Deploy to staging/testnet relayer
  • Test with a known historical deposit that was missed
  • Verify the deposit gets processed through the normal pipeline
  • Verify error handling for invalid/non-escrow transactions

🤖 Generated with Claude Code

danwt and others added 30 commits September 23, 2025 16:25
important things: tendermint -> comet, and cosmos chain refactor
danwt and others added 28 commits December 30, 2025 12:36
…rchain_gas_paymasters (#423)

Update code to use the plural Vec<H256> field name that was changed in
CoreContractAddresses. The scraper now uses .first().cloned().unwrap_or_default()
to get the first IGP address from the array.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…#424)

* claude: feat(kaspa): unify validator config into single kaspaValidators array

Replace three separate comma-separated config fields with a single array
of validator objects:

Before:
- validatorHosts: "host1,host2,..."
- validatorIsmAddresses: "addr1,addr2,..."
- validatorPubsKaspa: "pub1,pub2,..."

After:
- kaspaValidators: [
    {host: "host1", ismAddress: "addr1", escrowPub: "pub1"},
    ...
  ]

This eliminates the implicit ordering requirement between separate fields
and keeps related validator data together. The RelayerStuff struct now
stores the unified Vec<KaspaValidatorInfo> directly instead of parallel
vectors.

Closes dymensionxyz/hyperlane-deployments#64

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix(kaspa): sort signatures by ISM address for Hub ISM verification

The Hub ISM requires signatures to be in lexicographic order of validator
ISM addresses. This commit:

- Updates collect_with_threshold to return (index, value) pairs
- Sorts deposit signatures by recovered signer address
- Sorts confirmation signatures by config ISM address (using index lookup)
- Adds documentation about config ordering requirements
- Adds warning logging for invalid ISM address parsing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
)

* claude: feat(kaspa): add migration mode config and guards for key rotation

Add ValidationConf fields:
- migration_target_address: when set, enables migration mode
- previous_escrow_address: for confirmation across escrow boundary

Add is_migration_mode() helper and guard checks in handlers:
- respond_validate_new_deposits: returns error in migration mode
- respond_sign_pskts: returns error in migration mode
- confirmation endpoint remains enabled for migration

Config parsing updated to support:
- migrationTargetAddress JSON field
- previousEscrowAddress JSON field

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): remove redundant previous_escrow_address

During migration, the current escrow config IS the old escrow.
For confirmation boundary crossing, use migration_target_address
as the new escrow instead of a separate previous_escrow_address.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): rename validation toggles and remove unused config

- Rename deposit_enabled -> validate_deposits
- Rename withdrawal_enabled -> validate_withdrawals
- Rename withdrawal_confirmation_enabled -> validate_confirmations
- Remove unused previousEscrowAddress from config parsing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* claude: feat(kaspa): add migration TX validation and signing endpoint

Add /sign_migration endpoint for validators to sign migration PSKTs
during key rotation. The endpoint:

- Only accepts requests when migration mode is active
- Validates that PSKT spends from current escrow inputs
- Validates all outputs go to configured migration_target_address
- Signs escrow inputs with validator's Kaspa key

Also bumps hyperlane-cosmos-rs to include MigrationFxg proto.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: rewrite migration validation with proper security checks

Migration validation now implements all requirements from issue #192:

- Query hub for current anchor via outpoint() and verify PSKT spends it
- Query Kaspa for ALL escrow UTXOs and verify PSKT spends ALL of them
- Verify PSKT contains ONLY expected inputs (escrow UTXOs + hub anchor)
- Verify exactly ONE output to migration target address
- Verify payload is empty MessageIDs (no withdrawals during migration)
- Validate output amount doesn't exceed input amount

Also:
- Add parsed_migration_target() to ValidationConf for encapsulated parsing
- DRY escrow_input_selector() and calculate_escrow_input_sum() in withdraw.rs
- Pass hub_rpc and kaspa_grpc to migration validation in server.rs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix migration validation critical issues

Addresses review feedback:

1. Allow relayer fee inputs - removed "unexpected input" rejection since
   relayer needs to provide inputs to pay transaction fees

2. Verify escrow funds 100% preserved - now checks escrow_input_sum == output
   instead of output <= total_inputs (uses EscrowAmountMismatch error)

3. Verify UTXO amounts against Kaspa - query now returns amounts, and we
   verify each PSKT input amount matches what Kaspa reports for that outpoint

4. Fail on missing UTXO entries - now returns error instead of silently
   defaulting to 0 with map_or()

5. Use calculate_escrow_input_sum() from withdraw.rs for DRY escrow sum

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: extract query_hub_anchor to ops/withdraw.rs

DRY improvement - extracted hub anchor query logic:
- Added parse_hub_outpoint() helper for outpoint conversion
- Added query_hub_anchor() that uses withdrawal_status(vec![], None)
- Updated filter_pending_withdrawals() to use parse_hub_outpoint()
- Migration validation now uses shared query_hub_anchor()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor migration validation and DRY key loading

- Rename sign_withdrawal_fxg to sign_pskt_bundle (serves both uses)
- Extract load_keypair() method on KaspaEscrowKeySource to DRY key loading
- Allow relayer change outputs in migration validation (same pattern as withdrawals)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: revert unintended sealevel formatting changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…tion (#429)

Replace optional migration_target_address parameter with explicit
src_escrow and dst_escrow parameters. This is a cleaner mental model:
- Normally src and dst are the same (current escrow)
- During migration, src is old escrow and dst is new escrow

The validation accepts outputs to either address, enabling confirmation
traces to cross the migration boundary.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…er (#430)

* claude: feat(kaspa): support separate ISM and escrow validator lists in relayer

Post-migration, ISM signers (for deposits/confirmations) may differ from
escrow signers (for withdrawals). Add dual validator list support:

Changes to RelayerStuff:
- Add ism_validators: Option<Vec<KaspaValidatorInfo>>
- Add ism_validators() helper method for fallback to validators

Changes to ValidatorsClient:
- Add ism_validators() and ism_hosts() helpers
- Update get_deposit_sigs() to use ism_validators
- Update get_confirmation_sigs() to use ism_validators
- get_withdraw_sigs() continues using validators (escrow signers)

Config parsing:
- Parse optional kaspaIsmValidators JSON field
- Backwards compatible: when not set, uses validators for both

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): require both validator sets explicitly

Removes optional fallback behavior for ISM validators. Now both
validators_escrow and validators_ism are required fields in RelayerStuff.

Changes:
- Rename validators -> validators_escrow, ism_validators -> validators_ism
- Both fields are now required (no Option, no fallback)
- Rename validators() -> validators_escrow(), ism_validators() -> validators_ism()
- Rename hosts() -> escrow_hosts()
- Config keys: kaspaValidatorsEscrow, kaspaValidatorsIsm
- Add migrateEscrowTo config field

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix(kaspa): rename hosts methods and remove migration config

- Rename escrow_hosts() -> hosts_escrow(), ism_hosts() -> hosts_ism()
- Remove migrateEscrowTo (belongs in migration PR, not dual-lists PR)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): DRY up validator parsing functions

Extract parse_kaspa_validators_escrow and parse_kaspa_validators_ism
into a single parameterized parse_kaspa_validators function.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): address reviewer feedback

- Add expect() message for relayer_stuff unwrap (BLOCKING)
- Extract hosts_from helper to DRY up hosts_escrow/hosts_ism
- Simplify validators_escrow/validators_ism via relayer_stuff helper

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* claude: feat(kaspa): add migration command for escrow key rotation

Add relayer-side migration flow that:
- Fetches all UTXOs from current escrow
- Builds migration PSKT transferring funds to new escrow
- Collects signatures from validators via get_migration_sigs
- Combines bundles using multisig threshold
- Finalizes and broadcasts migration transactions

Also adds:
- ValidatorsClient.get_migration_sigs() for collecting validator signatures
- request_sign_migration_bundle() HTTP client function
- finalize_migration_txs() for migration transaction finalization
- Makes combine_all_bundles() public for reuse

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: DRY migration flow with shared helpers

- Use imported PopulatedInput type from dym_kas_core::pskt
- Use PopulatedInputBuilder instead of manual construction (fixes sequence value)
- Consolidate create_pskt with optional payload (removes duplicate function)
- Remove redundant threshold check (enforced by collect_with_threshold)
- Remove unnecessary Arc clone

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: feat(relayer): add CLI flag for escrow migration

Add migrateEscrowTo setting that enables migration mode. When set,
the relayer executes escrow key migration to the specified Kaspa
address and exits instead of running the normal relayer loop.

Usage: HYP_RELAYER_MIGRATEESCROWTO="kaspa:qz..." ./relayer

Changes:
- Add migrate_escrow_to field to RelayerSettings
- Add migration check at start of Relayer::run()
- Add run_escrow_migration() method
- Refactor execute_migration to take &KaspaProvider
- Re-export KaspaAddress from dymension_kaspa

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(relayer): move migrateEscrowTo to RelayerStuff config

Move the migration target address from RelayerSettings to the Kaspa-specific
RelayerStuff config object. This is the proper location since migration is
Kaspa-specific functionality.

Changes:
- Add migrate_escrow_to to RelayerStuff in conf.rs
- Parse migrateEscrowTo in connection_parser.rs
- Update relayer to read from KaspaProvider.must_relayer_stuff()
- Remove hacky migrate_escrow_to from RelayerSettings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(relayer): clean up migration code

Extract get_migration_target() helper and pass target address as parameter
to run_escrow_migration() to eliminate duplicate lookups and reduce nesting.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix(migration): align construction with validation requirements

Fix PR #432 migration construction to match PR #431 validation:

1. Payload: Use MessageIDs::new(vec![]).to_bytes() instead of None
   - Validation expects empty MessageIDs protobuf, not missing payload

2. Fee handling: Add relayer fee inputs and change output
   - Kaspa requires non-zero fees (fee = inputs - outputs)
   - Escrow funds are 100% preserved (escrow_sum == target_output)
   - Relayer pays fees from their own UTXOs with change returned

3. Hub anchor verification: Check hub anchor is among escrow UTXOs
   - Query hub for current anchor before building PSKT
   - Fail early if state is stale (anchor not in escrow UTXOs)

The migration PSKT now:
- Inputs: all escrow UTXOs + relayer fee UTXOs
- Outputs: migration target (escrow_sum) + relayer change (relayer_sum - fee)
- Payload: empty MessageIDs serialized as protobuf

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: format sealevel client code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix(kaspa): add relayer signing for migration fee inputs

Validators only sign escrow inputs, but the relayer fee inputs were
left unsigned, which would cause a panic at runtime during finalization.

Add sign_relayer_fee helper to sign relayer inputs after collecting
validator signatures, matching the withdrawal flow pattern.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): update trace function for src/dst escrow model

Changes expensive_trace_transactions to take separate src_escrow and
dst_escrow parameters instead of a single escrow_addresses parameter.

This allows the trace to cross the migration boundary when src (old escrow)
differs from dst (new escrow), enabling hub sync after key rotation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): extract ensure_hub_synced shared function

Extracts hub sync logic into a standalone function in sync.rs that can
be called from both the regular relayer loop and migration flow.

- ensure_hub_synced takes src/dst escrow addresses and a signature formatter
- sync_hub_if_needed now delegates to the shared function
- Allows hub sync after migration with different src/dst addresses

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: feat(kaspa): add retry loop and hub sync to migration

Adds robust error handling to escrow migration:

- Retry loop with exponential backoff (up to 10 attempts)
- Optional pre-migration sync (handles edge case where hub anchor is spent)
- Required post-migration sync (updates hub anchor to new escrow)
- Uses src=old_escrow, dst=new_escrow to trace across migration boundary

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): move migration logic to kas_hack module

Moves retry loop and hub sync logic from relayer.rs to kas_hack/migration.rs
to reduce Dymension-specific code in the main relayer file.

- run_migration_with_sync handles full migration flow with retries
- relayer.rs now just creates signature formatter and delegates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): move format_ad_hoc_signatures to kas_hack

Moves signature formatting function to sync.rs and exports from kas_hack.
relayer.rs now only creates the closure and delegates to kas_hack functions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): DRY up migration and withdrawal code

- Remove Option from create_pskt payload parameter (always required)
- Remove finalize_migration_txs, use finalize_txs for both flows
- Remove unused messages parameter from finalize_txs
- DRY up sign_relayer_fee - single function accepts &Bundle
- Simplify migration retry to fixed 1 minute delay

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): use real mass estimation for migration

Replace hardcoded estimate_migration_tx_mass with Kaspa's actual
MassCalculator via dym_kas_core::pskt::estimate_mass - same approach
used by withdrawal flow.

- Build placeholder outputs to calculate mass (amounts don't affect mass)
- Call estimate_mass with inputs, outputs, payload, network_id, escrow_m
- Calculate fee from actual mass
- Build final outputs with correct relayer change

This ensures migration fee calculation is accurate and consistent
with withdrawal fee calculation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix(kaspa): remove premature logs that violate 'log after action' rule

- Remove "Checking if hub is out of sync" log before any work is done
- Change "submitting confirmation" to "Traced transaction lineage" since trace
  is what just completed, and submission is logged separately after it happens

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix(kaspa): remove premature log and fix double clone in migration

- Remove "Starting escrow key migration" log before any work is done
- Fix double clone in TX submission loop: was cloning once before closure
  and once inside, now only clones inside closure per retry attempt

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…ic types (#436)

Split the unified KaspaValidatorInfo struct into two distinct types:
- KaspaValidatorEscrow: for escrow signers (host + escrow_pub)
- KaspaValidatorIsm: for ISM signers (host + ism_address)

This eliminates unused fields in each context:
- validators_escrow never used ism_address
- validators_ism never used escrow_pub

Also makes the generic parse_kaspa_validators function to handle both types.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Consolidate `migrationTargetAddress` (validator) and `migrateEscrowTo`
(relayer) into a single `migrate_escrow_to` field on `ConnectionConf`.

Both validators and relayers now use the same config field name
`migrateEscrowTo` in JSON and `HYP_CHAINS_<CHAIN>_MIGRATEESCROWTO` as
env var.

Changes:
- conf.rs: Move migrate_escrow_to from RelayerStuff to ConnectionConf,
  remove migration_target_address from ValidationConf, add helper
  methods is_migration_mode() and parsed_migration_target() to
  ConnectionConf
- connection_parser.rs: Remove migrationTargetAddress parsing
- provider.rs: Add conf() accessor method
- server.rs: Update validator to use res.conf().is_migration_mode()
- relayer.rs: Update to use kas_provider.conf().migrate_escrow_to

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Log migration mode status on validator startup when Kaspa chain is detected
- Add /migration-status endpoint returning is_migration_mode and migration_target

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add println! logging in ConnectionConf constructor to show migration
mode status before wallet connection. This ensures migration config
is visible in logs even if wallet connection fails (which happens
before tracing is initialized).

Output shows:
- is_migration_mode=true/false
- migration_target=Some("kaspatest:pz...") or None

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
… config (#441)

After escrow migration, validators need to update their config to point
to the new escrow. Without this check, a validator could restart with
old config and potentially sign invalid transactions.

The hub anchor is the source of truth - it always points to a UTXO in
the current escrow. By querying it at startup and comparing against
the configured escrow, stale config is detected immediately with a
clear, actionable error message.

Changes:
- Add EscrowConfigMismatch variant to ValidationError
- Add verify_escrow_matches_hub_anchor function in startup.rs
- Integrate into Kaspa validator startup flow
- Fails if hub is not bootstrapped (validator shouldn't run without hub)
- Extract get_output_address utility to reduce code duplication

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…ys (#443)

Show position, key value, and length when a public key fails to parse
in the escrow command. Makes debugging key rotation issues easier.

Before: "called Result::unwrap() on Err value: InvalidPublicKey"
After:  "invalid public key at position 1 (0-indexed): 'BADKEY' (len=6): malformed public key"

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…rays (#444)

The config parser was updated to expect kaspaValidatorsEscrow and
kaspaValidatorsIsm (commit 6427b67), but the validator config template
was never updated. This caused validators to load 0 pub keys, resulting
in ErrTooManyRequiredSigs (8 required > 0 keys).

Changes:
- Rename kaspaValidators -> kaspaValidatorsEscrow (escrow pub keys only)
- Add kaspaValidatorsIsm with ISM addresses from pubs.txt

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The config library's CaseAdapter converts all JSON keys to flat case
(e.g., escrowPub -> escrowpub), but the KaspaValidatorEscrow and
KaspaValidatorIsm structs expect camelCase keys via serde rename_all.

Adding serde aliases allows the structs to deserialize correctly when
keys are flat-cased by the config library.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The relayer was attempting to query all validators, including those
with empty host strings (indicating inactive/offline validators).
This caused unnecessary HTTP request failures and error logs.

Changes:
- hosts_escrow() and hosts_ism() now filter out empty hosts
- Original indices are preserved for correct signature/address correlation
- collect_with_threshold() now accepts (index, host) pairs to maintain
  index mapping for signature verification callbacks
- Error message clarifies count is of validators with non-empty hosts

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…ceholder (#447)

KIP-0009 storage mass divides by output amounts, so using zero as the
relayer change placeholder caused a divide-by-zero panic. Use relayer_sum
instead, matching the pattern in the withdrawal sweep flow.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
)

* claude: fix(kaspa): two-pass fee estimation for migration mass calculation

The single-pass approach underestimated the fee because storage mass is
C/output_amount — the actual change output (relayer_sum - fee) is smaller
than relayer_sum, yielding higher mass. Apply the same two-pass pattern
used in the withdrawal sweep flow, and use ceil() instead of round().

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix(kaspa): add priority fee buffer to migration fee estimation

Add RELAYER_SWEEPING_PRIORITY_FEE (3000 sompi) buffer to both passes,
matching the withdrawal sweep pattern. This covers any residual
underestimation from the fee-mass feedback loop.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously, if post-migration hub sync failed (e.g. TX not yet
confirmed), the retry loop would re-attempt the migration itself.
Since the old escrow is already empty, migration always fails and
sync is never retried.

Fix: split into two phases — migration with retry, then sync with
retry. If migration fails but new escrow has funds, detect that
migration already happened and skip straight to sync.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Migration transactions have no payload (no withdrawals processed),
but the trace function required a non-null payload. Treat null as
empty — no message IDs to extract.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* claude: fix(kaspa): handle null payload in transaction trace

Migration transactions have no payload (no withdrawals processed),
but the trace function required a non-null payload. Treat null as
empty — no message IDs to extract.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: fix(kaspa): handle null payload in validator confirmation check

Same issue as relayer trace — validators also reject null payload
with MissingTransactionPayload. Treat null as empty MessageIDs.
Remove unused MissingTransactionPayload error variant.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* claude: refactor(kaspa): remove max attempts from migration retry loops

Migration and hub sync now retry indefinitely instead of failing after
10 attempts. This is appropriate for an unattended process that must
eventually succeed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The startup check that verifies escrow config matches hub anchor
requires querying potentially old Kaspa transactions. The Kaspa
REST APIs (both api-tn10 and rest.tn.kaspa.rollapp.network) fail
to return old/spent transactions, causing validators to panic on
startup.

Removing this check to unblock the blumbus migration. The check
was a footgun prevention but querying old TXs is unreliable.

This reverts the changes from PR #441.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…#453)

When a validator enters migration mode, it writes a lock file containing
the migration target (new escrow address). When the validator later starts
in normal mode, it verifies the configured escrow matches the lock file.

This prevents operators from forgetting to update their escrow config
after a migration completes. The lock file serves as a reminder that must
be resolved (either by updating config or manually deleting the file).

Lock file behavior:
- Migration mode: writes `kaspa_migration.lock` with new escrow address
- Normal mode + lock exists + escrow matches: deletes lock, continues
- Normal mode + lock exists + escrow mismatch: panics with clear error
- Normal mode + no lock: continues (never migrated or manually cleared)

This replaces the previous approach (PR #441) which queried old Kaspa
transactions via REST API - those queries are unreliable for old/pruned TXs.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add signature verification for confirmation requests to match deposit
behavior. Previously, confirmations did not validate that the signature
came from the expected ISM address, allowing misconfigured validators
to return signatures from wrong keys.

Changes:
- Add verify_ism_signer() helper for DRY signature validation
- Update get_deposit_sigs() to use the shared helper
- Add signature verification to get_confirmation_sigs()
- Add SignableProgressIndication::new() constructor for external use
- Always validate (ISM addresses are always required for sorting)

Also includes minor formatting fixes from cargo fmt.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Extend the /validator-info endpoint to return both ISM address and
escrow public key. Both fields are now optional to support validators
that have only ISM key, only escrow key, or both.

Response format:
- ism_address: Ethereum-style address (if ISM signer configured)
- escrow_pub: Compressed secp256k1 public key hex (if escrow key configured)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a command to compute the Hyperlane message ID for Kaspa deposit
transactions. This enables users to verify whether a deposit has been
processed on the Dymension hub by querying with the computed message ID.

The message ID is deterministically derived from the deposit payload
combined with the Kaspa transaction metadata (tx_id, utxo_index).

Usage:
  kaspa-tools compute-deposit-id <payload> <tx_id> <utxo_index>

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add POST /kaspa/deposit/recover endpoint to manually recover deposits
that fell outside the normal lookback window due to relayer DB being
wiped or other issues.

The endpoint:
- Accepts a kaspa_tx ID
- Fetches the transaction from Kaspa REST API
- Validates it's a valid escrow transfer
- Queues it for processing via the existing deposit pipeline

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@danwt
Copy link
Copy Markdown
Author

danwt commented Jan 27, 2026

Reopening with correct base branch (main-dym)

@danwt danwt closed this Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants