diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a8cbf4b..1b192781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,49 +8,42 @@ Changes included since `v1.11.1` (range: `v1.11.1..v1.20.0`). Full EVM integration documentation: [docs/evm-integration/main.md](docs/evm-integration/main.md) -- Added Cosmos EVM v0.6.0 with four new modules: `x/vm` (EVM execution), `x/feemarket` (EIP-1559 dynamic base fee), `x/precisebank` (6-decimal `ulume` ↔ 18-decimal `alume` bridge), and `x/erc20` (STRv2 token pair registration + IBC middleware). -- Added dual-route ante handler (`app/evm/ante.go`) routing Ethereum extension txs to the EVM path and all others to the Cosmos path, with pending tx listener support. -- Added app-side EVM mempool (`app/evm_mempool.go`) with Ethereum-like sender ordering, nonce-gap handling, and same-nonce replacement rules. -- Added async broadcast queue (`app/evm_broadcast.go`) to prevent mempool mutex re-entry deadlock during nonce-gap promotion. -- Added 11 static precompiles: P256, Bech32, Staking, Distribution, ICS20, Bank, Gov, Slashing, plus custom Action (`0x0901`), Supernode (`0x0902`), and Wasm (`0x0903`) precompiles for Lumera-specific EVM→Cosmos and EVM→CosmWasm calls. -- Added JSON-RPC server and indexer enabled by default with 7 namespaces; optional per-IP rate limiting proxy (`app/evm_jsonrpc_ratelimit.go`) with configurable token bucket. -- Added EVM tracing support configurable at runtime via `app.toml [evm] tracer` (json, struct, access_list, markdown). -- Added OpenRPC discovery: `rpc_discover` JSON-RPC method, `GET /openrpc.json` HTTP endpoint with CORS, gzip-compressed spec embedded in binary (315 KB → 20 KB), and build-time generation via `tools/openrpcgen`. -- Changed default key type to `eth_secp256k1` and BIP44 coin type from 118 to 60 for Ethereum-compatible wallet derivation (MetaMask, Ledger). -- Added EVM chain ID `76857769`, base fee `0.0025 ulume/gas`, min gas price floor `0.0005 ulume/gas` (prevents zero-fee spam), and base fee change denominator `16` (~6.25% adjustment per block). -- Added IBC ERC20 middleware wired on both v1 and v2 transfer stacks with governance-controlled registration policy (`all`/`allowlist`/`none`) via `MsgSetRegistrationPolicy`. -- Added `x/evmigration` module for legacy coin-type-118 → 60 account migration with dual-signature verification and multi-module atomic state re-keying (auth, bank, staking, distribution, authz, feegrant, supernode, action, claim); a separate `MsgMigrateValidator` flow re-keys the validator operator, deletes the orphaned legacy KV row, and rejects jailed validators with operator guidance. -- Added multisig migration support: `LegacyProof` proto with single-key + multisig `oneof` variants, `MaxMultisigSubKeys` param (default 20), and a K/N mirror-source consensus rule requiring sub-key count and threshold to match between legacy and new sides. Verifier helpers (`verifySecp256k1Sig`, `verifySingleKeyProof`, `verifyMultisigProof`) include duplicate-sub-key preflight, `signer_indices`/sub-key uniqueness checks, and a defense-in-depth `ValidateProofPair` at the message-server boundary. -- Added a four-step offline multisig CLI flow (`generate-proof-payload` → `sign-proof` → `combine-proof` → `submit-proof`) so co-signers can participate without sharing keys; `combine-proof` verifies each partial cryptographically before assembling the final proof, surfacing tampered partials before on-chain submission. -- Added user-facing migration helper scripts (`scripts/migrate-account.sh`, `scripts/migrate-validator.sh`, `scripts/migrate-multisig.sh`) wrapping the full pre-flight estimate → key import → snapshot → submit → verify flow, with multisig-aware K/N partials, validator-specific cap checks and downtime acknowledgment, and fail-closed query handling so script-level success implies on-chain success. -- Added `devnet/scripts/lumera-helper.sh unjail-validator` helper plus downtime warnings in the validator migration guide for operators approaching the slashing window. -- Added fee-waiving ante decorator for migration txs (`ante/evmigration_fee_decorator.go`) since new addresses have zero balance pre-migration. -- Added migration-aware mempool signer extractor (`app/evmigration_signer_extraction_adapter.go`) wired into `ExperimentalEVMMempool.CosmosPoolConfig.SignerExtractor`. Without it, the SDK's default `DefaultSignerExtractionAdapter` rejects zero-signer migration txs (`MsgClaimLegacyAccount`, `MsgMigrateValidator`) with "tx must have at least one signer" during app-side mempool admission/proposal selection, blocking `submit-proof` broadcast. The adapter synthesizes a deterministic signer from the message's `legacy_address` for migration-only txs and delegates everything else to the EVM-aware default. -- Added regression coverage for zero-signer evmigration tx admission: unit tests pin the upstream SDK mempool rejection, adapter fallback and negative cases (malformed `legacy_address`, nonexistent legacy accounts, multi-message and mixed-message txs), app tests cover `PrepareProposal` inclusion and disabled-mempool wiring, and real-node integration tests broadcast `submit-proof`-style tx bytes through CometBFT `broadcast_tx_sync`. -- Hardened the migration ante (`x/evmigration/keeper/ante.go`) to enforce the migration admission window and cheap state plausibility before mempool admission: since migration txs are fee-free and signature-free, `VerifyMigrationProofsForAnte` now rejects them with `ErrMigrationDisabled`/`ErrMigrationWindowClosed` when `EnableMigration` is off or `MigrationEndTime` has passed, and rejects proof-valid but impossible migrations such as nonexistent legacy accounts, already-migrated sources, reused destination addresses, and non-validator `MsgMigrateValidator` sources. This bounds the zero-fee mempool-spam surface to the operator-defined window (no-op under default params; mainnet sets a concrete `MigrationEndTime`) and avoids retaining txs that would fail immediately at message execution. -- Added v1.20.0 upgrade handler with store additions for feemarket, precisebank, vm, erc20, and evmigration; post-migration finalization sets Lumera EVM params, feemarket params, and ERC20 defaults. -- Added Action module precompile (`0x0901`) and Supernode module precompile (`0x0902`) giving Solidity contracts native access to `MsgRequestAction`/`MsgFinalizeAction` (including LEP-5 cascade availability commitments) and supernode queries/registration respectively. -- Added CosmWasm ↔ EVM cross-runtime bridge (Phase 1, non-payable, depth-1 reentrancy guard): `WasmPrecompile` at `0x0903` exposes `execute`, `query`, `contractInfo`, `rawQuery` to Solidity, and a custom Wasm message handler + query handler decorator (`app/wasm_evm_plugin.go`) lets CosmWasm contracts invoke EVM contracts via `ApplyMessage` with an explicitly-constructed `statedb`. Cross-runtime gas is capped at `DefaultCrossRuntimeGasCap = 3,000,000` per call. -- Added blocked-address protections: module accounts and all precompile addresses are excluded from bank sends to prevent accidental token loss. -- Added centralized bank denom metadata (`config/bank_metadata.go`) and `RegisterExtraInterfaces` for `eth_secp256k1` crypto interface registration across SDK + EVM paths. -- Added `RegisterTxService` override (`app/evm_runtime.go`) to capture the local CometBFT client for the async broadcast worker, replacing the stale HTTP client that `SetClientCtx` provides before CometBFT starts. -- Added depinject custom signer wiring for `MsgEthereumTx` and safe early-RPC keeper coin info initialization (`SetKeeperDefaults`) to prevent panics before genesis runs. -- CosmWasm (`wasmd v0.61.6` + `wasmvm v3.0.3`) and EVM coexist in the same runtime — Lumera is the only Cosmos chain shipping both simultaneously, and the cross-runtime bridge above lets contracts call across the boundary in either direction. -- Added evmigration query endpoints for migration planning and monitoring: `MigrationEstimate` (pre-migration impact analysis with delegation/unbonding/redelegation/authz/feegrant counts), `MigrationStats` (on-chain progress tracking), `LegacyAccounts` (paginated unmigrated account listing), and `MigratedAccounts` (searchable migration history). -- Added dual signature verification in evmigration: legacy proofs accept both raw SHA-256 CLI signing and ADR-036 wallet signing (Keplr/Leap); new address proofs accept both raw Keccak-256 and EIP-191 `personal_sign` (MetaMask), ensuring compatibility across all major wallet types. -- Added `app.toml` auto-config migration (`cmd/lumera/cmd/config_migrate.go`) for nodes upgrading from pre-EVM binaries — automatically detects missing `[evm]`, `[json-rpc]`, `[tls]`, and `[lumera.*]` sections and regenerates `app.toml` with Lumera defaults while preserving existing operator settings. -- Updated app-side mempool defaults to keep fresh testnet/mainnet-style homes bounded at `mempool.max-txs = 10000`; config migration now rewrites legacy no-op `max-txs = -1` to `5000` on devnet and `10000` on testnet/mainnet, while preserving the real Cosmos EVM v0.6.0 `[evm.mempool]` defaults (`global-slots = 5120`, `global-queue = 1024`). -- Added EVM mempool Prometheus metrics (`app/evm_mempool_metrics.go`): gauges for mempool size, pending/queued counts, and broadcast queue depth; labeled rejection counter (`rejections_total{source,reason}`) for observability. -- Added `MsgSetRegistrationPolicy` governance message for ERC20 IBC auto-registration: operators can toggle policy between `all`, `allowlist`, and `none` modes; pre-populated genesis allowlist includes inert base denom traces for major tokens (uatom, uosmo, uusdc, inj) ready for governance channel binding. -- Added evmigration user guides under `docs/evm-integration/user-guides/`: `migration.md` (CLI/Keplr/MetaMask account migration), `validator-migration.md`, `supernode-migration.md`, and `migration-scripts.md` reference for the helper scripts above. -- Added node operator EVM configuration guide (`docs/evm-integration/user-guides/node-evm-config-guide.md`) and tuning guide (`docs/evm-integration/user-guides/tune-guide.md`) covering `app.toml` tuning, RPC exposure, tracer config, and rate limit setup. -- Added comprehensive EVM integration test suites under `tests/integration/evm/` covering ante, contracts, feemarket, IBC ERC20, JSON-RPC, mempool, precisebank, precompiles, and VM queries. -- Added devnet evmigration end-to-end tests validating the full legacy account migration flow across a multi-validator network, plus multisig-mode coverage (single-key, multisig-of-secp256k1, and multisig-of-eth destinations) and `PermanentLocked` vesting fixtures. -- Added `make devnet-evm-upgrade` and versioned 1.11.1 devnet targets to exercise the on-chain `v1.11.1 → v1.20.0` upgrade path end-to-end against the multi-validator devnet. -- Renamed the devnet upload service from `network-maker` to `lumera-uploader` across docs, dockerfile, and lifecycle scripts; legacy binary names are still recognized by `devnet/scripts/stop.sh` for backwards compatibility. -- Updated transitive Go dependencies (CosmWasm, go-ethereum, and others) to address critical and high-severity security vulnerabilities surfaced by Go module audit. -- Bumped transitive Go modules `github.com/go-chi/chi/v5` (5.2.3 → 5.2.4) and `github.com/quic-go/quic-go` (0.54.1 → 0.59.1, with `quic-go/qpack` 0.5.1 → 0.6.0) from Dependabot; verified with `go build ./...`, `make lint`, and a live multi-validator devnet run. -- Migrated the `precompiles/solidity` example/dev toolchain from Hardhat 2 to Hardhat 3 (`@nomicfoundation/hardhat-toolbox-mocha-ethers`, ESM, `network.create()` connection API, ethers-v6-native typechain) and pinned patched transitives (`lodash-es`, `serialize-javascript`, `diff`) via npm `overrides`, cutting `npm audit` findings from 50 to 11 with 0 critical/high/moderate remaining. All 11 precompile integration tests pass against the live devnet. +This release integrates a full EVM execution layer (Cosmos EVM v0.6.0) alongside the existing CosmWasm runtime, and adds the `x/evmigration` module for migrating legacy accounts to Ethereum-compatible keys. + +### EVM execution layer + +- Added Cosmos EVM v0.6.0 with four modules: `x/vm` (EVM execution), `x/feemarket` (EIP-1559 dynamic base fee), `x/precisebank` (6-decimal `ulume` ↔ 18-decimal `alume` bridge), and `x/erc20` (STRv2 token pairs + IBC middleware). CosmWasm (`wasmd v0.61.6`) and EVM now coexist in the same runtime. +- **Breaking:** changed default key type to `eth_secp256k1` and BIP44 coin type from 118 to 60 for Ethereum-compatible wallet derivation (MetaMask, Ledger). +- Added EVM chain ID `76857769`, EIP-1559 base fee `0.0025 ulume/gas`, and a min gas price floor `0.0005 ulume/gas` (prevents zero-fee spam). +- Added a JSON-RPC server and indexer enabled by default (7 namespaces), OpenRPC discovery (`rpc_discover`, `GET /openrpc.json`), runtime-configurable tracing, and an optional per-IP rate-limiting proxy. +- Added an app-side EVM mempool with Ethereum-like sender ordering and nonce-gap handling, an async broadcast queue, and Prometheus metrics (size, pending/queued, broadcast depth, labeled rejections). +- Added a dual-route ante handler that routes Ethereum extension txs to the EVM path and all others to the Cosmos path. + +### Precompiles & cross-runtime bridge + +- Added 11 static precompiles (P256, Bech32, Staking, Distribution, ICS20, Bank, Gov, Slashing) plus custom Action (`0x0901`) and Supernode (`0x0902`) precompiles giving Solidity contracts native access to action requests/finalization (incl. LEP-5 cascade commitments) and supernode queries/registration. +- Added a CosmWasm ↔ EVM cross-runtime bridge (Phase 1): a Wasm precompile (`0x0903`) lets Solidity call CosmWasm contracts, and a custom message/query handler lets CosmWasm contracts call EVM contracts. Cross-runtime gas is capped at 3,000,000 per call. +- Added blocked-address protections so module accounts and precompile addresses are excluded from bank sends, preventing accidental token loss. + +### IBC ERC20 + +- Added IBC ERC20 middleware on both v1 and v2 transfer stacks with a governance-controlled registration policy (`all`/`allowlist`/`none`) via `MsgSetRegistrationPolicy`. + +### Account migration (`x/evmigration`) + +- Added the `x/evmigration` module for migrating legacy coin-type-118 accounts to coin-type-60 (Ethereum-compatible) addresses, with dual-signature verification and atomic multi-module state re-keying across auth, bank, staking, distribution, authz, feegrant, supernode, action, and claim. A separate `MsgMigrateValidator` flow re-keys the validator operator and rejects jailed validators. +- Added multisig migration with a K/N mirror-source consensus rule and a four-step offline CLI flow (`generate-proof-payload` → `sign-proof` → `combine-proof` → `submit-proof`) so co-signers participate without sharing keys. +- Added dual signature verification across all major wallet types: legacy proofs accept raw CLI (SHA-256) and ADR-036 (Keplr/Leap); new-address proofs accept raw Keccak-256 and EIP-191 `personal_sign` (MetaMask). +- Migration txs are fee-free and signature-free; a hardened ante enforces the migration window and rejects implausible migrations (nonexistent/already-migrated sources, reused destinations) before mempool admission to bound zero-fee spam. +- Added query endpoints for migration planning and monitoring: `MigrationEstimate`, `MigrationStats`, `LegacyAccounts`, and `MigratedAccounts`. +- Added user-facing helper scripts (`migrate-account.sh`, `migrate-validator.sh`, `migrate-multisig.sh`) and user guides for account, validator, and supernode migration. + +### Upgrade & operations + +- Added the `v1.20.0` upgrade handler with store additions for the new EVM and evmigration modules and post-upgrade finalization of Lumera EVM, fee market, and ERC20 params. The handler auto-derives `migration_end_time` from the upgrade-block time (devnet +2 days; testnet and mainnet +3 calendar months). +- Added `app.toml` auto-config migration for nodes upgrading from pre-EVM binaries — detects missing `[evm]`, `[json-rpc]`, `[tls]`, and `[lumera.*]` sections and regenerates them with Lumera defaults while preserving operator settings. +- Added a node-operator EVM configuration guide and a tuning guide covering `app.toml`, RPC exposure, tracer config, and rate limiting. +- Updated transitive Go dependencies (CosmWasm, go-ethereum, quic-go, and others) to address critical and high-severity security vulnerabilities, and migrated the example Solidity toolchain to Hardhat 3. --- diff --git a/app/upgrades/v1_20_0/upgrade.go b/app/upgrades/v1_20_0/upgrade.go index 1ce66813..552caa69 100644 --- a/app/upgrades/v1_20_0/upgrade.go +++ b/app/upgrades/v1_20_0/upgrade.go @@ -26,26 +26,30 @@ import ( // UpgradeName is the on-chain name used for this upgrade. const UpgradeName = "v1.20.0" -// Devnet and testnet derive a finite migration_end_time automatically from the -// upgrade block time so rehearsals and public testnet exercise a real deadline. -// Mainnet instead receives a specific absolute migration_end_time chosen near -// its own upgrade (the handler leaves it at the default 0). -const ( - devnetMigrationWindow = 2 * 24 * time.Hour - testnetMigrationWindow = 7 * 24 * time.Hour -) - -// autoMigrationWindow returns the migration window to auto-apply for the given -// chain ID, or 0 if the deadline should be left to a manually chosen timestamp -// (mainnet and any unrecognized chain ID). -func autoMigrationWindow(chainID string) time.Duration { +// Recognized Lumera networks (devnet/testnet/mainnet) derive a finite +// migration_end_time automatically from the upgrade block time so they run +// against a real deadline without hardcoding an absolute timestamp. Devnet uses +// a short fixed window for rehearsals; testnet and mainnet both run against a +// 3-calendar-month window measured from the upgrade block. Unrecognized chain +// IDs (custom networks/forks) are left with no deadline. +const devnetMigrationWindow = 2 * 24 * time.Hour + +// autoMigrationEndTime returns the migration_end_time to auto-apply for the +// given chain ID and upgrade block time, plus whether a deadline should be set +// at all. Unrecognized chain IDs leave the deadline unset (ok == false). +// +// A calendar-month offset (AddDate) is used for testnet/mainnet rather than a +// fixed time.Duration because "3 months" is not a constant number of hours; it +// must be applied to the block time directly so month lengths and leap years +// are handled correctly. +func autoMigrationEndTime(chainID string, blockTime time.Time) (time.Time, bool) { switch { case lcfg.IsDevnetChainID(chainID): - return devnetMigrationWindow - case lcfg.IsTestnetChainID(chainID): - return testnetMigrationWindow + return blockTime.Add(devnetMigrationWindow), true + case lcfg.IsTestnetChainID(chainID), lcfg.IsMainnetChainID(chainID): + return blockTime.AddDate(0, 3, 0), true default: - return 0 + return time.Time{}, false } } @@ -153,14 +157,20 @@ func CreateUpgradeHandler(p appParams.AppUpgradeParams) upgradetypes.UpgradeHand } } - // On devnet and testnet, derive a finite migration_end_time from the - // upgrade block time so those networks run against a real deadline without - // hardcoding an absolute timestamp. RunMigrations already seeded the - // evmigration module with default params (enable_migration=true, - // migration_end_time=0); here we only override the deadline. Mainnet keeps - // the default 0 at upgrade and receives a specific migration_end_time - // chosen separately near launch. - if window := autoMigrationWindow(p.ChainID); window > 0 { + // Derive a finite migration_end_time from the upgrade block time so the + // network runs against a real deadline without hardcoding an absolute + // timestamp. RunMigrations already seeded the evmigration module with + // default params (enable_migration=true, migration_end_time=0); here we + // only override the deadline. Devnet gets a short rehearsal window; + // testnet and mainnet both get a 3-calendar-month window. + // + // The network is identified from the SDK context (ctx.ChainID()), which + // carries the genesis-derived chain ID from the block header. We must not + // use the app-level ChainID captured during setupUpgrades: that value + // comes from the --chain-id flag, which defaults to the non-empty + // "lumera" and so never falls back to genesis, leaving mainnet's deadline + // silently unset on the common `lumerad start` path. + if endTime, ok := autoMigrationEndTime(ctx.ChainID(), ctx.BlockTime()); ok { if p.EvmigrationKeeper == nil { return nil, fmt.Errorf("%s upgrade requires evmigration keeper to be wired", UpgradeName) } @@ -168,14 +178,13 @@ func CreateUpgradeHandler(p appParams.AppUpgradeParams) upgradetypes.UpgradeHand if err != nil { return nil, fmt.Errorf("get evmigration params: %w", err) } - emParams.MigrationEndTime = ctx.BlockTime().Add(window).Unix() + emParams.MigrationEndTime = endTime.Unix() if err := p.EvmigrationKeeper.Params.Set(ctx, emParams); err != nil { return nil, fmt.Errorf("set evmigration migration_end_time: %w", err) } p.Logger.Info("Set migration_end_time from upgrade block time", - "chain_id", p.ChainID, + "chain_id", ctx.ChainID(), "migration_end_time", emParams.MigrationEndTime, - "window", window.String(), ) } diff --git a/app/upgrades/v1_20_0/upgrade_test.go b/app/upgrades/v1_20_0/upgrade_test.go index 6d5881eb..a42c1ab8 100644 --- a/app/upgrades/v1_20_0/upgrade_test.go +++ b/app/upgrades/v1_20_0/upgrade_test.go @@ -40,7 +40,12 @@ func upgradeParamsForChain(app *lumeraapp.App, chainID string) appParams.AppUpgr // block time (block time + 2 days) so rehearsals run against a real deadline. func TestV1200SetsDevnetMigrationEndTime(t *testing.T) { app := lumeraapp.Setup(t) - ctx := app.BaseApp.NewContext(false) + // The handler identifies the network from the SDK context (genesis-derived + // chain ID), not the app-level ChainID captured during setup — which on a + // default `lumerad start` is the non-empty "lumera" flag default. Seed the + // context with the real chain ID and pass the misleading default in params + // to pin that ctx.ChainID() is what's used. + ctx := app.BaseApp.NewContext(false).WithChainID("lumera-devnet-1") // Default genesis params seed migration with no deadline. before, err := app.EvmigrationKeeper.Params.Get(ctx) @@ -49,7 +54,7 @@ func TestV1200SetsDevnetMigrationEndTime(t *testing.T) { want := ctx.BlockTime().Add(2 * 24 * time.Hour).Unix() - handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera-devnet-1")) + handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera")) _, err = handler(sdk.WrapSDKContext(ctx), upgradetypes.Plan{}, module.VersionMap{}) require.NoError(t, err) @@ -62,38 +67,65 @@ func TestV1200SetsDevnetMigrationEndTime(t *testing.T) { "max_validator_delegations default should be 2500") } -// On testnet the handler derives a 7-day migration window from the upgrade -// block time. +// On testnet the handler derives a 3-calendar-month migration window from the +// upgrade block time. func TestV1200SetsTestnetMigrationEndTime(t *testing.T) { app := lumeraapp.Setup(t) - ctx := app.BaseApp.NewContext(false) + // See TestV1200SetsDevnetMigrationEndTime: network is taken from ctx.ChainID(). + ctx := app.BaseApp.NewContext(false).WithChainID("lumera-testnet-2") - want := ctx.BlockTime().Add(7 * 24 * time.Hour).Unix() + want := ctx.BlockTime().AddDate(0, 3, 0).Unix() - handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera-testnet-1")) + handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera")) _, err := handler(sdk.WrapSDKContext(ctx), upgradetypes.Plan{}, module.VersionMap{}) require.NoError(t, err) after, err := app.EvmigrationKeeper.Params.Get(ctx) require.NoError(t, err) require.Equal(t, want, after.MigrationEndTime, - "testnet upgrade should set migration_end_time to upgrade block time + 7 days") + "testnet upgrade should set migration_end_time to upgrade block time + 3 months") } -// Mainnet keeps migration_end_time at the default 0 at upgrade; a specific -// absolute timestamp is chosen and applied separately near launch. -func TestV1200LeavesMigrationEndTimeZeroOnMainnet(t *testing.T) { +// On mainnet the handler derives a 3-calendar-month migration window from the +// upgrade block time, the same window as testnet. +func TestV1200SetsMainnetMigrationEndTime(t *testing.T) { app := lumeraapp.Setup(t) - ctx := app.BaseApp.NewContext(false) + // See TestV1200SetsDevnetMigrationEndTime: network is taken from ctx.ChainID(). + // This is the case the review flagged — the app-level ChainID would be the + // "lumera" default, leaving the mainnet deadline silently unset if used. + ctx := app.BaseApp.NewContext(false).WithChainID("lumera-mainnet-1") + + // Default genesis params seed migration with no deadline. + before, err := app.EvmigrationKeeper.Params.Get(ctx) + require.NoError(t, err) + require.Equal(t, int64(0), before.MigrationEndTime) + + want := ctx.BlockTime().AddDate(0, 3, 0).Unix() + + handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera")) + _, err = handler(sdk.WrapSDKContext(ctx), upgradetypes.Plan{}, module.VersionMap{}) + require.NoError(t, err) + + after, err := app.EvmigrationKeeper.Params.Get(ctx) + require.NoError(t, err) + require.Equal(t, want, after.MigrationEndTime, + "mainnet upgrade should set migration_end_time to upgrade block time + 3 months") +} + +// The bare "lumera" CLI default is not a real network ID; the handler must not +// treat it as one and must leave migration_end_time unset (0). +func TestV1200LeavesMigrationEndTimeUnsetForDefaultChainID(t *testing.T) { + app := lumeraapp.Setup(t) + ctx := app.BaseApp.NewContext(false).WithChainID("lumera") - handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera-mainnet-1")) + handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera")) _, err := handler(sdk.WrapSDKContext(ctx), upgradetypes.Plan{}, module.VersionMap{}) require.NoError(t, err) after, err := app.EvmigrationKeeper.Params.Get(ctx) require.NoError(t, err) require.Equal(t, int64(0), after.MigrationEndTime, - "mainnet upgrade must leave migration_end_time at the default 0") + "unrecognized chain ID must leave migration_end_time at the default 0") } func TestV1200InitializesERC20ParamsWhenInitGenesisIsSkipped(t *testing.T) { diff --git a/docs/evm-integration/architecture/rollout.md b/docs/evm-integration/architecture/rollout.md index becc95bc..c757fbb0 100644 --- a/docs/evm-integration/architecture/rollout.md +++ b/docs/evm-integration/architecture/rollout.md @@ -114,7 +114,7 @@ The short list that genuinely needs a value chosen before tagging is the `x/evmi | Parameter | Code default | Live mainnet signal | Decision | | --- | --- | --- | --- | | `enable_migration` | `true` (immediate-open) | — | **Keep `true` — immediate-open.** Migration opens as soon as the upgraded chain produces blocks; the upgrade handler leaves `enable_migration=true` with no controlled-open gating. Public messaging, support, and RPC capacity must therefore be live before validators restart. | -| `migration_end_time` | `0` (no deadline) | 160,021 accounts and 83 validators to migrate | **Auto-set by the `v1.20.0` upgrade handler from the upgrade-block time:** devnet = + **2 days**, testnet = + **7 days** (no hardcoded timestamp). **Mainnet:** the handler leaves it `0`; set a specific absolute Unix timestamp (seconds) — a chosen wall-clock end time, ~**+120 days** past the planned upgrade — closer to launch. Must be non-zero before mainnet while the proof format has no expiry. | +| `migration_end_time` | `0` (no deadline) | 160,021 accounts and 83 validators to migrate | **Auto-set by the `v1.20.0` upgrade handler from the upgrade-block time (no hardcoded timestamp):** devnet = + **2 days**, testnet and mainnet = + **3 calendar months** (`AddDate(0, 3, 0)`). The handler derives the deadline on every recognized network, so mainnet is non-zero from the first post-upgrade block while the proof format has no expiry. | | `max_validator_delegations` | `2500` (raised from 2000) | worst-case validator migration-object count is **1,632** (`chain-helper.sh` reports `max_observed: 1632`, `suggested_cap: 2122` at the default 30% buffer) | **Shipped default is now `2500`** — clears the worst case (1,632) with ~1.53× headroom; `3000` judged over-provisioned given the short runway. Re-check with `chain-helper.sh` near tag time. | | `max_migrations_per_block` | `50` | at 50/block (~5s blocks) the full 160k account base clears in ~11 days of continuous saturation | **Keep `50`.** Adequate for a weeks-long window; only revisit if load tests show RPC or block-production stress. | | `max_multisig_sub_keys` | `20` | not enumerable from public state — multisig pubkeys are only revealed on-chain after the group's first tx | **Keep `20`** unless the internal key inventory knows of a larger foundation/treasury multisig; confirm against that inventory rather than chain state. | @@ -156,7 +156,7 @@ The short list that genuinely needs a value chosen before tagging is the `x/evmi | Parameter / decision | Current code default | Required decision before tag | Why it matters | Verification | | --- | --- | --- | --- | --- | | `enable_migration` | `true` | **Decided: immediate-open.** Keep `enable_migration=true`; the upgrade handler leaves it enabled so migration opens at the first post-upgrade block | if `true`, migration can open as soon as the upgraded chain produces blocks | `lumerad q evmigration params` immediately after upgrade | -| `migration_end_time` | `0` meaning no deadline | **Decided:** auto-set by the upgrade handler from upgrade-block time — devnet + 2 days, testnet + 7 days; mainnet left `0` by the handler and given a specific absolute Unix timestamp (~+120 days) chosen near launch | an unlimited window extends support and proof-replay risk indefinitely | query params and compare the Unix timestamp to the public migration-window announcement | +| `migration_end_time` | `0` meaning no deadline | **Decided:** auto-set by the upgrade handler from upgrade-block time — devnet + 2 days, testnet and mainnet + 3 calendar months; no network is left at `0` | an unlimited window extends support and proof-replay risk indefinitely | query params and compare the Unix timestamp to the public migration-window announcement | | `max_migrations_per_block` | `50` | **Decided: keep `50`** — adequate per-block claim throttle; revisit only if RC/devnet load tests show stress | too high can stress blocks/RPC; too low can create user backlog | run migration traffic at the proposed limit alongside normal Cosmos/EVM traffic | | `max_validator_delegations` | `2500` | **Decided & shipped: `2500`** (raised from 2000) — clears the live worst case of 1,632 migration objects (`chain-helper.sh` `max_observed: 1632`) with ~1.53× headroom; `3000` judged over-provisioned given the short runway | validator migrations iterate delegations, unbondings, and redelegations; under-sizing blocks large validators, over-sizing increases per-tx work | run `scripts/chain-helper.sh max-validator-delegations --json` (works today in `staking-pre-evm` mode) and confirm the chosen cap exceeds the observed maximum plus buffer | | `max_multisig_sub_keys` | `20` | **Decided: keep `20`** — confirm against the internal key inventory rather than chain state | larger values increase proof size, gas, and coordinator complexity | rehearse multisig migrations at or near the proposed ceiling and verify ante / keeper rejection above it | @@ -172,7 +172,7 @@ Before testnet and mainnet, choose one of these activation policies and test tha - **Immediate-open policy**: migration is available immediately after the upgrade. This is operationally simpler, but public messaging, support staffing, RPC capacity, and migration monitoring must be live before validators restart. - **Controlled-open policy**: migration remains disabled until post-upgrade smoke tests pass. This requires an implementation or governance path that is already effective at upgrade time; a normal post-upgrade parameter proposal is too slow to prevent an initial open window. -**Decision:** Lumera uses the **immediate-open policy** — `enable_migration=true` at the first post-upgrade block — paired with a finite `migration_end_time` (a specific absolute Unix timestamp in seconds, compared against block time). On **devnet and testnet** the `v1.20.0` upgrade handler sets this automatically from the upgrade-block time (devnet + **2 days**, testnet + **7 days**). On **mainnet** the handler leaves it `0`; a specific absolute timestamp (~**120 days** past the upgrade) is chosen and applied near launch. Because migration opens immediately, public messaging, support staffing, RPC capacity, and migration monitoring must be live before validators restart. +**Decision:** Lumera uses the **immediate-open policy** — `enable_migration=true` at the first post-upgrade block — paired with a finite `migration_end_time` (a specific absolute Unix timestamp in seconds, compared against block time). The `v1.20.0` upgrade handler sets this automatically from the upgrade-block time on every recognized network: devnet + **2 days**, and **testnet and mainnet + 3 calendar months** (`AddDate(0, 3, 0)`). Because migration opens immediately, public messaging, support staffing, RPC capacity, and migration monitoring must be live before validators restart. Mainnet should not rely on an assumed manual "open the window" step; the release candidate must prove that `enable_migration=true` and the intended `migration_end_time` are already in state at the first post-upgrade block. diff --git a/docs/evm-integration/assets/evmigration-1.png b/docs/evm-integration/assets/evmigration-1.png index 10f7bf86..78f3d5aa 100644 Binary files a/docs/evm-integration/assets/evmigration-1.png and b/docs/evm-integration/assets/evmigration-1.png differ diff --git a/docs/evm-integration/assets/evmigration-10.png b/docs/evm-integration/assets/evmigration-10.png index 77f10731..9ee212ee 100644 Binary files a/docs/evm-integration/assets/evmigration-10.png and b/docs/evm-integration/assets/evmigration-10.png differ diff --git a/docs/evm-integration/assets/evmigration-11.png b/docs/evm-integration/assets/evmigration-11.png index 7c1b601e..17005b17 100644 Binary files a/docs/evm-integration/assets/evmigration-11.png and b/docs/evm-integration/assets/evmigration-11.png differ diff --git a/docs/evm-integration/assets/evmigration-12.png b/docs/evm-integration/assets/evmigration-12.png index ed486406..34d39b54 100644 Binary files a/docs/evm-integration/assets/evmigration-12.png and b/docs/evm-integration/assets/evmigration-12.png differ diff --git a/docs/evm-integration/assets/evmigration-13.png b/docs/evm-integration/assets/evmigration-13.png new file mode 100644 index 00000000..820d96b4 Binary files /dev/null and b/docs/evm-integration/assets/evmigration-13.png differ diff --git a/docs/evm-integration/assets/evmigration-14.png b/docs/evm-integration/assets/evmigration-14.png new file mode 100644 index 00000000..36fc607c Binary files /dev/null and b/docs/evm-integration/assets/evmigration-14.png differ diff --git a/docs/evm-integration/assets/evmigration-15.png b/docs/evm-integration/assets/evmigration-15.png new file mode 100644 index 00000000..e960c679 Binary files /dev/null and b/docs/evm-integration/assets/evmigration-15.png differ diff --git a/docs/evm-integration/assets/evmigration-16.png b/docs/evm-integration/assets/evmigration-16.png new file mode 100644 index 00000000..c5067677 Binary files /dev/null and b/docs/evm-integration/assets/evmigration-16.png differ diff --git a/docs/evm-integration/assets/evmigration-17.png b/docs/evm-integration/assets/evmigration-17.png new file mode 100644 index 00000000..528805d2 Binary files /dev/null and b/docs/evm-integration/assets/evmigration-17.png differ diff --git a/docs/evm-integration/assets/evmigration-18.png b/docs/evm-integration/assets/evmigration-18.png new file mode 100644 index 00000000..c341c49a Binary files /dev/null and b/docs/evm-integration/assets/evmigration-18.png differ diff --git a/docs/evm-integration/assets/evmigration-19.png b/docs/evm-integration/assets/evmigration-19.png new file mode 100644 index 00000000..7725f774 Binary files /dev/null and b/docs/evm-integration/assets/evmigration-19.png differ diff --git a/docs/evm-integration/assets/evmigration-2.png b/docs/evm-integration/assets/evmigration-2.png index f2d31500..b0ba3d30 100644 Binary files a/docs/evm-integration/assets/evmigration-2.png and b/docs/evm-integration/assets/evmigration-2.png differ diff --git a/docs/evm-integration/assets/evmigration-20.png b/docs/evm-integration/assets/evmigration-20.png new file mode 100644 index 00000000..350e16cf Binary files /dev/null and b/docs/evm-integration/assets/evmigration-20.png differ diff --git a/docs/evm-integration/assets/evmigration-21.png b/docs/evm-integration/assets/evmigration-21.png new file mode 100644 index 00000000..4b7cb6d5 Binary files /dev/null and b/docs/evm-integration/assets/evmigration-21.png differ diff --git a/docs/evm-integration/assets/evmigration-22.png b/docs/evm-integration/assets/evmigration-22.png new file mode 100644 index 00000000..d2b2210b Binary files /dev/null and b/docs/evm-integration/assets/evmigration-22.png differ diff --git a/docs/evm-integration/assets/evmigration-23.png b/docs/evm-integration/assets/evmigration-23.png new file mode 100644 index 00000000..f93834cf Binary files /dev/null and b/docs/evm-integration/assets/evmigration-23.png differ diff --git a/docs/evm-integration/assets/evmigration-24.png b/docs/evm-integration/assets/evmigration-24.png new file mode 100644 index 00000000..a70aef1b Binary files /dev/null and b/docs/evm-integration/assets/evmigration-24.png differ diff --git a/docs/evm-integration/assets/evmigration-25.png b/docs/evm-integration/assets/evmigration-25.png new file mode 100644 index 00000000..20b43ac9 Binary files /dev/null and b/docs/evm-integration/assets/evmigration-25.png differ diff --git a/docs/evm-integration/assets/evmigration-3.png b/docs/evm-integration/assets/evmigration-3.png index 2bdc7c06..3db1d224 100644 Binary files a/docs/evm-integration/assets/evmigration-3.png and b/docs/evm-integration/assets/evmigration-3.png differ diff --git a/docs/evm-integration/assets/evmigration-4.png b/docs/evm-integration/assets/evmigration-4.png index f20455b9..0fa9a21d 100644 Binary files a/docs/evm-integration/assets/evmigration-4.png and b/docs/evm-integration/assets/evmigration-4.png differ diff --git a/docs/evm-integration/assets/evmigration-4ex.png b/docs/evm-integration/assets/evmigration-4ex.png deleted file mode 100644 index f04481d6..00000000 Binary files a/docs/evm-integration/assets/evmigration-4ex.png and /dev/null differ diff --git a/docs/evm-integration/assets/evmigration-5.png b/docs/evm-integration/assets/evmigration-5.png index 95212d9c..873abec2 100644 Binary files a/docs/evm-integration/assets/evmigration-5.png and b/docs/evm-integration/assets/evmigration-5.png differ diff --git a/docs/evm-integration/assets/evmigration-6.png b/docs/evm-integration/assets/evmigration-6.png index 7997b300..9188fb68 100644 Binary files a/docs/evm-integration/assets/evmigration-6.png and b/docs/evm-integration/assets/evmigration-6.png differ diff --git a/docs/evm-integration/assets/evmigration-7.png b/docs/evm-integration/assets/evmigration-7.png index a7490325..f35f04cf 100644 Binary files a/docs/evm-integration/assets/evmigration-7.png and b/docs/evm-integration/assets/evmigration-7.png differ diff --git a/docs/evm-integration/assets/evmigration-8.png b/docs/evm-integration/assets/evmigration-8.png index 00485fd9..b3a14782 100644 Binary files a/docs/evm-integration/assets/evmigration-8.png and b/docs/evm-integration/assets/evmigration-8.png differ diff --git a/docs/evm-integration/assets/evmigration-9.png b/docs/evm-integration/assets/evmigration-9.png index 95223741..1e888a50 100644 Binary files a/docs/evm-integration/assets/evmigration-9.png and b/docs/evm-integration/assets/evmigration-9.png differ diff --git a/docs/evm-integration/user-guides/migration.md b/docs/evm-integration/user-guides/migration.md index f9205b53..7e240a42 100644 --- a/docs/evm-integration/user-guides/migration.md +++ b/docs/evm-integration/user-guides/migration.md @@ -1,6 +1,6 @@ # EVM Legacy Account Migration - User Guide -**Last updated**: 2026-06-16 +**Last updated**: 2026-06-24 **Applies to**: Lumera chain with `x/evmigration` module enabled (post-EVM upgrade) --- @@ -117,19 +117,21 @@ This is the easiest method. The Lumera Portal provides a guided wizard that hand The Portal exposes the same Lumera chain through two profiles in the top-left network picker: -- **lumera-devnet / lumera-testnet-2 / lumera-mainnet-1** (legacy profile) —`bip44.coinType: 118`, no EVM features. Lets users with legacy 118-derived wallets see their pre-migration account. +- **lumera-devnet-1 / lumera-testnet-2 / lumera-mainnet-1** (legacy profile) —`bip44.coinType: 118`, no EVM features. Lets users with legacy 118-derived wallets see their pre-migration account. - **lumera-devnet-evm / lumera-testnet-evm / lumera-mainnet-evm** (EVM profile) —`bip44.coinType: 60`,`eth-secp256k1-cosmos` features enabled. Lets users with the post-migration EVM-derived wallet see their migrated state. Both profiles connect to the **same on-chain network** (the same `chain_id`). What differs is which `bip44.coinType` and which address-derivation style the Portal asks Keplr to use. You can migrate from either profile — the wizard derives the destination EVM address through Keplr's Ethereum provider regardless — but after migration you'll generally end up on the EVM profile to see your migrated balance. -### The chain/profile state panel +### The EVM Migration page and its state panel -Every page in the **EVM Account Migration** section starts with a state panel that summarises four pieces of context. Watching these four rows is the single most reliable way to understand what the Portal sees and which follow-up step (if any) is still pending: +Migration now has its own dedicated page. Open **EVM Migration** from the left-hand navigation (it sits below the chain menu items). The page is titled **EVM Account Migration** and opens with a **Migration Status** section. Before you connect a wallet, that section already shows two context rows (**on-chain network** and **Portal profile**), the **Migration Window** countdown, and global progress stats; the two Keplr rows appear once you connect. + +The state panel at the top of **Migration Status** summarises four pieces of context. Watching these four rows is the single most reliable way to understand what the Portal sees and which follow-up step (if any) is still pending: | Row | What it means | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **on-chain network** | The `chain_id` of the connected node, plus a tag indicating the chain has EVM migration support (`/ EVM support`). | -| **Portal profile** | Which JSON profile the Portal is currently using (`lumera-devnet` or `lumera-devnet-evm`) and the `coin-type` it's configured for. Yellow when on the legacy profile (`118`), green on the EVM profile (`60`). | +| **Portal profile** | Which JSON profile the Portal is currently using (`lumera-devnet-1` or `lumera-devnet-evm`) and the `coin-type` it's configured for. Yellow when on the legacy profile (`118`), green on the EVM profile (`60`). | | **Keplr chain config** | The `bip44.coinType` Keplr has stored for this `chain_id` in its chain registry — independent of which profile the Portal is on. Yellow when Keplr is still on `118`, green on `60`. | | **Keplr account key** | Which derivation Keplr is actually serving for the connected wallet (`legacy key / coin-type 118` or `EVM key / coin-type 60`). The Portal infers this by recomputing both bech32 variants from Keplr's pubkey and matching against `walletStore.currentAddress` (with a migration-record cross-check). | @@ -137,34 +139,53 @@ When **all four rows are green**, your wallet, Keplr, and the Portal are fully a ### Step-by-Step Guide -#### 1. Connect Your Wallet and Check Migration Status +#### 1. Open the EVM Migration page and connect your wallet + +Make sure Keplr has your legacy account selected (you'll see its legacy balance on the Dashboard while on the legacy `lumera-devnet-1` profile): + +![Portal dashboard on the legacy profile with Keplr showing the legacy account](../assets/evmigration-1.png) + +In the Portal, open **EVM Migration** from the left-hand navigation. This opens the dedicated **EVM Account Migration** page. Before you connect, the **Migration Status** section already shows the on-chain network, the current Portal profile, the **Migration Window** countdown, and global progress stats; the **START MIGRATION WIZARD** button is disabled until a wallet is connected: -Navigate to the Lumera Portal and go to the **Claim** page. Scroll to the **EVM Account Migration** section. +![EVM Account Migration page before connecting a wallet](../assets/evmigration-2.png) -Click **Connect Wallet**. If the Lumera chain isn't yet registered in Keplr, the Portal will prompt you to approve it via Keplr's `suggestChain` dialog (screenshot 10 below shows the EVM-profile variant). +Click **Connect Wallet** (top-right) and choose **Keplr**: -The state panel summarises what the Portal currently sees, the migration progress dashboard reports global migration counters, and the **Connected Wallet Address** panel shows your address along with a status badge. If you have a legacy (coin-type 118) account with on-chain state, you'll see the **"Ready to Migrate"** badge with a summary of your assets: +![Connect Wallet dialog — Keplr selected](../assets/evmigration-3.png) -![Portal claim page — legacy profile, legacy account ready for migration](../assets/evmigration-1.png) +If the Lumera chain isn't yet registered in Keplr, the Portal will prompt you to approve it via Keplr's `suggestChain` dialog (the EVM-profile variant is shown later in the post-migration flow). + +Once connected, the state panel fills in all four rows, and the **Connected Wallet Address** section shows your address with a status line. If you have a legacy (coin-type 118) account with on-chain state, you'll see **"Legacy account ready for migration"** and a **Ready to Migrate** breakdown of your assets: + +![EVM Account Migration page — legacy account connected and ready to migrate](../assets/evmigration-4.png) In this screenshot: -- The state panel shows**Portal profile: lumera-devnet / coin-type 118**,**Keplr chain config: coin-type 118**,**Keplr account key: legacy key / coin-type 118** — all in yellow (legacy 118 derivation everywhere). The**on-chain network** row confirms`lumera-devnet-1 / EVM support`. -- **Account Migration Progress** shows global counters (e.g.`5 / 46 accounts` migrated, refreshed every 5 minutes). -- The**Ready to Migrate** badge under the connected address breaks down what will move: +- The state panel shows **Portal profile: lumera-devnet-1 / coin-type 118**, **Keplr chain config: coin-type 118**, **Keplr account key: legacy key / coin-type 118** — all in yellow (legacy 118 derivation everywhere). The **on-chain network** row confirms `lumera-devnet-1 / EVM support`. +- The **Migration Window** card shows how long migration stays open (e.g. `1d 22h 35m left`) and the exact close time. This reflects the chain's `migration_end_time` parameter; when it shows no deadline, migration has no time limit. +- The progress stats report global counters, refreshed every 5 minutes: + - **Migrated** — accounts already migrated + - **Remaining** — accounts still to migrate, split into **with key** (have signed on-chain, so a key is known) and **without key** (never signed) + - **Staked (legacy)** — legacy accounts still holding delegations + - **Validators** — migrated / total validators +- The **Ready to Migrate** breakdown under the connected address shows what will move: - **Balance** — your available LUME balance - **Delegations** — active staking delegations - **Unbonding** — pending unbonding entries - **Authz Grants / Feegrants** — authorization and fee grant counts - **Supernode** — whether this account runs a supernode +> **Multisig account?** The page has a separate **Migrate a Multisig Account** section at the bottom with an address field and a **CHECK MULTISIG** preflight button. The wizard itself does not support multisig — see [§ Migrating a multisig account](#migrating-a-multisig-account). + Click **START MIGRATION WIZARD** to begin. #### 2. Step 1: Review The wizard opens (modal title: **EVM Legacy Account Migration**) on **Step 1: Review**. Verify that the information is correct before proceeding: -![Step 1: Review — eligibility, addresses, and balance summary](../assets/evmigration-2.png) +![Step 1: Review — eligibility, addresses, and balance summary](../assets/evmigration-5.png) + +A note under the eligibility banner reminds you this is a **preliminary check** — the chain performs additional validation (migration window, rate limits, address uniqueness) when the transaction is actually submitted. Key things to check: @@ -183,43 +204,41 @@ Click **NEXT** when ready. #### 3. Step 2: Sign & Confirm -This step collects two cryptographic proofs that authenticate you as the owner of both the legacy and new addresses. No private keys leave your device — both signatures are produced locally in Keplr. +This step collects two cryptographic proofs that authenticate you as the owner of both the legacy and new addresses. No private keys leave your device — both signatures are produced locally in Keplr. The wizard spells this out: when you click the button, Keplr opens **two pop-ups, one after the other**; each only *signs a message* — no tokens move, no fee is charged, and nothing is sent on-chain yet. -![Step 2: Sign & Confirm — both proofs unsigned, transaction summary](../assets/evmigration-3.png) +![Step 2: Sign & Confirm — both proofs unsigned, transaction summary](../assets/evmigration-6.png) -Click **SIGN MIGRATION PROOFS**. Keplr opens **two signature popups** in sequence: +Click **SIGN MIGRATION PROOFS**. The wizard tracks progress inline ("Waiting for you to approve the first (legacy) pop-up in Keplr…") while Keplr opens **two signature popups** in sequence. **First popup — Legacy proof (ADR-036 signArbitrary):** -![Keplr signature request for legacy proof — ADR-036 format](../assets/evmigration-4.png) +![Wizard signing state with the Keplr legacy-proof popup (ADR-036)](../assets/evmigration-7.png) This is the legacy account proof. Notice: -- **"Signing with"** shows your Keplr wallet name (e.g. `my-legacy-acc`). -- **"on lumera-devnet"** — the Lumera chain. -- **"with lumera1jen0vglekw...57d5qn0xqg"** — your legacy address. +- **"Signing with"** shows your Keplr wallet name (e.g. `legacy-acc`). +- **"on lumera-devnet-1"** — the Lumera chain (Keplr's Cosmos signing provider). +- **"with lumera1rzmeg8fta4…ls0nmdx2uh"** — your legacy address. - **Message** is the migration payload string: `lumera-evm-migration:{chainID}:{evmChainID}:claim:{legacyAddr}:{newAddr}`. -- The collapsed **Advanced** drawer holds the full ADR-036 JSON sign doc (`sign/MsgSignData`) — the standard Cosmos arbitrary-message format. Expand it to inspect the fields: - - ![ADR-036 advanced view — full sign doc with sign/MsgSignData](../assets/evmigration-4ex.png) +- The collapsed **Advanced** drawer holds the full ADR-036 JSON sign doc (`sign/MsgSignData`) — the standard Cosmos arbitrary-message format. Expand it if you want to inspect the raw fields. Click **Approve** to sign with your legacy key. **Second popup — New proof (EIP-191 personal_sign):** -![Keplr signature request for new proof — Ethereum personal_sign](../assets/evmigration-5.png) +![Wizard with the legacy proof signed and the Keplr new-proof popup (Ethereum personal_sign)](../assets/evmigration-8.png) -This is the new (EVM) address proof. Notice the differences: +Once the legacy proof is signed the wizard advances ("Legacy proof signed. Now approve the second (new) pop-up in Keplr…") and Keplr opens the second popup. This is the new (EVM) address proof. Notice the differences: - **"on Ethereum"** — Keplr is using its Ethereum signing provider this time, not the Cosmos one. -- **"with 0x2b750d6a4c...1ab71f99ee"** — your Ethereum hex address. +- **"with 0x8fe663865b…31529109d2"** — your Ethereum hex address. - **Message** is the same migration payload string. Click **Approve** to sign with your new (coin-type 60) key. -When both signatures land, the wizard updates: the button reads **BOTH PROOFS SIGNED** and each line shows a green check: +When both signatures land, the wizard updates: the button reads **BOTH PROOFS SIGNED**, each line shows a green check, and the confirmation checkbox becomes active: -![Step 2 completed — both proofs signed, confirmation checkbox](../assets/evmigration-6.png) +![Step 2 completed — both proofs signed, confirmation checkbox](../assets/evmigration-9.png) The transaction summary lists **From** (legacy, 118) and **To** (new, 60) and confirms **Fee: None (fee-free)**. @@ -227,20 +246,21 @@ Tick **"I understand this is irreversible and all on-chain state will move to my #### 4. Migration Result -The Portal broadcasts the transaction and waits for confirmation (typically one block, 5–6 seconds). On success: +The Portal broadcasts the transaction and waits for confirmation (typically one block, 5–6 seconds). On success the wizard shows a **Migration Result** screen — *"Migration Successful! All on-chain state has been moved to your new address. Follow the steps below to finish setting up your wallet."* -![Migration Result — Migration Successful with new address, eth hex, and tx hash](../assets/evmigration-7.png) +![Migration Result — Migration Successful with the full post-migration checklist](../assets/evmigration-10.png) -The result screen shows: +The result screen now embeds the complete post-migration checklist so you can finish without leaving the dialog: -- **New address** — your post-migration Lumera bech32. -- **Ethereum hex** — the 0x-prefixed equivalent. -- **Tx** — the on-chain transaction hash for verification. -- A note pointing you back to the Claim page:*"Close this dialog and follow the Migration Successful instructions on the Claim page, or follow the Migration User Guide."* +- A **Next steps** heading with a **COPY CHECKLIST** button (copies the whole sequence to your clipboard). +- A **Multiple legacy accounts?** callout: keep Portal and Keplr on the legacy network until every legacy account is migrated, then do the cleanup once at the end (see the batching note further below). +- **1. New Lumera address** and **2. Ethereum hex address** — your post-migration addresses, each with a copy button. +- **3. Switch the Portal to Lumera EVM, reconnect Keplr, then add an existing wallet with the same recovery phrase** — the ordered sub-steps (a–e) that the Claim/EVM Migration page also walks you through after you close the dialog. +- **Tx** — the on-chain transaction hash, with copy and explorer-link buttons. -**For validators**: an urgent section underneath shows the restart command (`systemctl start lumerad`). Restart your validator promptly to avoid missed blocks and jailing. +**For validators**: an urgent section shows the restart command (`systemctl start lumerad`). Restart your validator promptly to avoid missed blocks and jailing. -Click **DONE** to close the wizard. The Claim page now switches into the post-migration follow-up flow described next. +Click **DONE** to close the wizard. The **EVM Migration** page now switches into the post-migration follow-up flow described next. > **Migrating more than one legacy account? Batch the wizards first, do the cleanup once at the end.** > @@ -248,81 +268,107 @@ Click **DONE** to close the wizard. The Claim page now switches into the post-mi > > Recommended order when migrating multiple legacy accounts: > -> 1. **Stay on the legacy Portal profile** (`lumera-devnet` /`lumera-mainnet`) and the original Keplr chain definition for the entire migration phase. +> 1. **Stay on the legacy Portal profile** (`lumera-devnet-1` /`lumera-mainnet-1`) and the original Keplr chain definition for the entire migration phase. > 2. After the wizard closes for account 1, ignore the**Wallet Re-Import Still Required** card for now. -> 3. In Keplr, click your wallet name (top-left) and switch to the next legacy account in the wallet list. The Connected Wallet Address on the Claim page updates automatically. +> 3. In Keplr, click your wallet name (top-left) and switch to the next legacy account in the wallet list. The Connected Wallet Address on the EVM Migration page updates automatically. > 4. The Portal will detect it as another "Legacy account ready for migration" — click**START MIGRATION WIZARD** and run through Step 1 → Step 2 → Migrate again. > 5. Repeat steps 3–4 for every legacy account you have. > 6. **Only once every legacy account is migrated**, follow the post-migration cleanup once: switch Portal to the EVM profile, refresh Keplr's chain registration, and then re-import the mnemonic(s) into fresh Keplr profile(s) to expose the migrated EVM-derived addresses for each account. > > **Many accounts? Use the shell helpers instead.** Once you're past a handful of legacy accounts, clicking through the Portal+Keplr wizard for each one becomes the bottleneck — and Keplr's signature popups can't be automated. Switch to the bundled [`scripts/migrate-account.sh`](#method-2-shell-helper-scripts) (or `migrate-validator.sh` for validators), which run the same migration non-interactively from a keyring. They're easy to drop into a loop over a list of legacy key names, produce structured exit codes for each result, and capture pre/post balance snapshots — so a batch migration is auditable rather than something you have to retrace by hand. -#### 5. Post-Migration Follow-Up on the Claim Page +#### 5. Post-Migration Follow-Up on the EVM Migration Page -After the wizard closes, the Claim page shows a **Migration Successful** card whose contents adapt to the *current* state of your Portal profile, Keplr chain config, and Keplr account key. There are four possible states; you'll move through them in order until everything is green. +After the wizard closes, the **EVM Migration** page shows a **Migration Successful** card whose contents adapt to the *current* state of your Portal profile, Keplr chain config, and Keplr account key. Your funds are already safe at the new address — the remaining work is a **per-Keplr-installation** cleanup so your wallet and the Portal both render the new EVM-derived address. The four state names below (A → D) are checkpoints you pass through; the linear walkthrough that follows takes you from A to D in order. -##### State A: "Migration Successful — Wallet Re-Import Still Required" (legacy Portal profile) +##### State A: "Wallet Re-Import Still Required" (still on the legacy profile) Right after the wizard closes you're still on the legacy Portal profile, so the page looks like this: -![Post-migration on legacy Portal profile — Wallet Re-Import Still Required](../assets/evmigration-8.png) +![Post-migration on the legacy Portal profile — Wallet Re-Import Still Required](../assets/evmigration-11.png) + +The state panel still reads `Portal profile: lumera-devnet-1 / coin-type 118` (yellow), `Keplr chain config: coin-type 118` (yellow), and `Keplr account key: legacy key / coin-type 118` (yellow). The Portal knows your migration record from the chain ("Account migrated from legacy …" appears under the connected address) but the connected key is still the legacy 118 derivation, so your displayed Keplr balance is 0 — the assets now live at the new EVM address. The card states the **main action** directly: *re-import the same mnemonic in Keplr and use the new profile derived from coin-type 60.* The migration record (legacy address, new Lumera address, **Migration date**, **Block height**) is shown at the bottom. + +Work through the cleanup in the order below. + +###### a. Remove the legacy Lumera chain in Keplr + +In Keplr, open the **☰** menu (top-right) and choose **Add/Remove Chains**: + +![Keplr menu with Add/Remove Chains](../assets/evmigration-12.png) -The state panel still reads `Portal profile: lumera-devnet / coin-type 118` (yellow) and `Keplr account key: legacy key / coin-type 118` (yellow). The Portal knows your migration record from the chain ("Account migrated from legacy …" appears under the connected address) but the connected key is still the legacy 118 derivation, so your displayed balance is 0 — the assets now live at the new EVM address. +Find the legacy **lumera-devnet-1** entry and toggle it **off**. (Removing it now avoids a stale `coin-type 118` chain definition lingering in Keplr's registry.) -The **Migration Successful — Wallet Re-Import Still Required** card lays out the **main action** and the ordered sub-steps: +![Keplr Add/Remove Chains — toggle the legacy lumera-devnet-1 chain off](../assets/evmigration-13.png) -1. **Disconnect** your wallet in the Portal. -2. In the Portal, click**Lumera Network** (top-left) and select**lumera-devnet-evm** — this switches the Portal to the EVM profile, which makes Keplr re-suggest the chain with`coin-type 60` + EVM features. -3. **Connect Keplr again.** When the Portal asks Keplr to add the EVM chain, accept it. -4. In Keplr, click your wallet name →**+** button →**Import existing wallet**. -5. Enter the**same recovery phrase** and select the newly imported profile. +###### b. Switch the Portal to the EVM profile -The migration record summary at the bottom shows the legacy address, the new Lumera address, the **Migration date**, and the **Block height**. +Click **Lumera Network** (top-left) and select **Lumera-Devnet-Evm**: -##### State B: "Migration Successful — Update Keplr Chain Definition" (Portal on EVM, Keplr chain still 118) +![Portal network picker — Lumera-Devnet-Evm and Lumera-Devnet-1 profiles](../assets/evmigration-14.png) -If you only switch the Portal profile (step 2 above) without finishing the rest of the flow, the page shifts to: +The page reloads on the EVM profile. The **Portal profile** row is now green (`lumera-devnet-evm / coin-type 60`), and the wallet is disconnected: -![Post-migration on EVM Portal profile, Keplr chain still 118 — Update Keplr Chain Definition](../assets/evmigration-9.png) +![EVM Migration page on the EVM profile, wallet disconnected](../assets/evmigration-15.png) -The state panel now reads `Portal profile: lumera-devnet-evm / coin-type 60` (green) but `Keplr chain config: coin-type 118` (yellow) and `Keplr account key: legacy key / coin-type 118` (yellow) — Keplr's chain registry hasn't been updated yet. +###### c. Reconnect Keplr and approve the EVM chain -A **"Connected to the migrated legacy account"** explainer appears, followed by the **Update Keplr Chain Definition** action card: +Click **Connect Wallet**. On the EVM profile the dialog now also offers **MetaMask** alongside Keplr; choose **Keplr**: -1. **Disconnect** your wallet in the Portal. -2. In Keplr, open**Settings** from the top-right corner. -3. Open**Add/Remove Chains**. -4. Find the legacy Lumera Network entry and toggle it off. -5. Back in the Portal, click**Connect Wallet** again. The Portal will re-suggest the EVM chain definition; approve it in Keplr: +![Connect Wallet on the EVM profile — Keplr and MetaMask options](../assets/evmigration-16.png) -![Keplr suggestChain dialog — Lumera EVM chain (coin-type 60, eth_secp256k1, eth-secp256k1-cosmos features)](../assets/evmigration-10.png) +The Portal asks Keplr to add the EVM chain definition. Approve the `suggestChain` dialog (`bip44.coinType: 60`, `features: ["eth-address-gen", "eth-key-sign", "eth-secp256k1-cosmos"]`): -This refreshes Keplr's chain registry to the EVM definition (`bip44.coinType: 60`, `features: ["eth-address-gen", "eth-key-sign", "eth-secp256k1-cosmos"]`). +![Keplr suggestChain dialog — Add lumera-devnet-evm (coin-type 60)](../assets/evmigration-17.png) -##### State C: "Migration Successful — Wallet Re-Import Still Required" (Portal on EVM, Keplr chain 60, but the *vault* still holds the 118 key) +> **Checkpoint — State B ("Update Keplr Chain Definition").** If you reconnected *before* removing the legacy chain in step a, the card reads **Update Keplr Chain Definition** instead: `Portal profile` is green but `Keplr chain config` is still `coin-type 118` (yellow). Disconnect, remove the legacy chain (step a), then reconnect so the Portal re-suggests the EVM definition. -After Keplr's chain config is on `60` but you haven't re-imported the mnemonic yet, the same Keplr profile is still serving its original 118-derived key, just rendered in eth-style for the new chain config: +###### d. Checkpoint — State C ("vault still holds the 118 key") -![Post-migration on EVM Portal profile + EVM Keplr chain config, but vault still on 118 — Wallet Re-Import Still Required](../assets/evmigration-11.png) +After the chain config is on `60` but before you re-import the mnemonic, the same Keplr profile is still serving its original 118-derived key, just rendered eth-style for the new chain config. The state panel shows the first three rows green but **Keplr account key: legacy key / coin-type 118** still yellow: -The state panel shows `Portal profile: lumera-devnet-evm / coin-type 60` and `Keplr chain config: coin-type 60` — both green — but `Keplr account key: legacy key / coin-type 118` is still yellow. The **Migration Successful — Wallet Re-Import Still Required** card asks you to finish the flow by importing the mnemonic into a fresh Keplr profile (the steps mirror sub-items d–e from State A). +![State panel — Portal and chain on coin-type 60, but Keplr account key still legacy 118](../assets/evmigration-18.png) -> **Why a fresh profile rather than just using the existing one?** A Keplr wallet profile derives its keys from the mnemonic at *creation time* using the chain's then-current `bip44.coinType`. Existing profiles aren't re-derived when the chain config later changes. Importing the same mnemonic into a new profile, after the chain registry is on `coin-type 60`, makes Keplr derive the EVM-compatible (P_60) key for that profile. +> **Why a fresh profile rather than just using the existing one?** A Keplr wallet profile derives its keys from the mnemonic at *creation time* using the chain's then-current `bip44.coinType`. Existing profiles aren't re-derived when the chain config later changes. Importing the same mnemonic into a new profile, after the chain registry is on `coin-type 60`, makes Keplr derive the EVM-compatible (P_60) key. + +###### e. Re-import the mnemonic into a fresh Keplr profile + +In Keplr, click your wallet name (top-left) to open **Select Wallet**, then click the **+** button: + +![Keplr Select Wallet — the + (add wallet) button](../assets/evmigration-19.png) + +Choose **Import an existing wallet**: + +![Keplr — Create / Import an existing wallet / Connect Hardware Wallet](../assets/evmigration-20.png) + +Choose **Use recovery phrase or private key**: + +![Keplr — Use recovery phrase or private key vs Connect with Google](../assets/evmigration-21.png) + +Enter the **same recovery phrase** you used for the legacy account (12- or 24-word, whichever you have): + +![Keplr Import Existing Wallet — recovery phrase entry](../assets/evmigration-22.png) + +Give the new profile a name (e.g. `evm-acc`) and click **Next**: + +![Keplr Set Up Your Wallet — name the new profile](../assets/evmigration-23.png) + +Select the chains to enable and click **Save**: + +![Keplr Select Chains — final import step](../assets/evmigration-24.png) ##### State D: "Migration Successful" — clean state (everything aligned) -Once you select the freshly-imported wallet profile, the state panel goes fully green and the card reduces to a brief confirmation: +Select the freshly-imported wallet profile. The state panel goes fully green and the card reduces to a brief confirmation: -![After re-import — clean state, all four rows green, migration record visible](../assets/evmigration-12.png) +![After re-import — clean state, all four rows green, migration record visible](../assets/evmigration-25.png) - **Portal profile**:`lumera-devnet-evm / coin-type 60` (green) - **Keplr chain config**:`coin-type 60` (green) - **Keplr account key**:`EVM key / coin-type 60` (green) - **Connected wallet address** is now your post-migration bech32, matching`migrationRecord.new_address`. -The card body says *"Your wallet and Portal are already on the migrated EVM address."* The migration record is displayed with the legacy address, new Lumera address, **Ethereum hex**, **Migration date**, and **Block height**. - -The old `Lumera (Legacy)` chain entry can be removed from Keplr at any point after this — it's no longer needed. +The card body says *"Your wallet and Portal are already on the migrated EVM address."* The migration record is displayed with the legacy address, new Lumera address, **Ethereum hex**, **Migration date**, and **Block height**. Keplr now shows the new profile (e.g. `evm-acc`) serving the EVM-derived address. ### Troubleshooting