Skip to content

Derive Avalanche C-Chain addresses at coin type 60#1057

Open
j0ntz wants to merge 2 commits into
masterfrom
jon/fix-avax-evm-derivation
Open

Derive Avalanche C-Chain addresses at coin type 60#1057
j0ntz wants to merge 2 commits into
masterfrom
jon/fix-avax-evm-derivation

Conversation

@j0ntz

@j0ntz j0ntz commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Avalanche C-Chain is EVM-compatible, and standard wallets (Exodus, MetaMask, Trust) derive its address at Ethereum's BIP-44 coin type 60. Edge derived Avalanche at coin type 9000 (the SLIP-44 X/P-Chain type), so a 12-word seed imported from one of those wallets produced an AVAX receive address that did not match the source wallet, the bug reported on the Asana task (ETH/POL matched, AVAX did not).

This switches avalancheInfo.hdPathCoinType from 9000 to 60, bringing AVAX in line with every other EVM network in this repo.

Existing wallets are unaffected. edge-core-js caches each wallet's derived public key (getPublicWalletInfo), and the avalanche plugin defines no checkPublicKey hook, so already-created AVAX wallets keep their cached coin-type-9000 address and funds. Only newly created or imported AVAX wallets adopt the corrected path.

Verification

  • Added a regression test (test/ethereum/avalancheDerivation.test.ts) asserting the avalanche plugin's derivePublicKey produces the known coin-type-60 EVM address (0x21D45Fd06e291C49AbFa135460DE827b6579Cef5) from an imported seed, the exact value shown on the receive scene.
  • tsc, eslint, and the full test suite pass (verify-repo.sh).
  • The fixed build was linked into edge-react-gui (via updot) and confirmed loading in the running iOS app on the simulator.

Asana: https://app.asana.com/0/1215088146871429/1209918996092096


Note

Medium Risk
Changes HD derivation for new/imported Avalanche wallets; existing wallets keep cached keys per PR description, but any code path that re-derives without cache could diverge from old 9000 addresses.

Overview
Fixes Avalanche C-Chain receive addresses for new/imported wallets by changing BIP-44 derivation from coin type 9000 to 60 in avalancheInfo, matching Exodus, MetaMask, Trust and other EVM wallets.

Documents the fix in CHANGELOG and adds test/ethereum/avalancheDerivation.test.ts, which asserts derivePublicKey for ethereum, polygon, and avalanche all yield the same known coin-type-60 address from a fixed 12-word seed.

Reviewed by Cursor Bugbot for commit 92a67d2. Bugbot is set up for automated code reviews on this repo. Configure here.

j0ntz added 2 commits June 9, 2026 18:24
AVAX C-Chain is EVM-compatible and every standard wallet (Exodus, MetaMask,
Trust) derives it at Ethereum's coin type 60. Edge used coin type 9000, so a
seed imported from those wallets produced a non-matching AVAX receive address.

Switch avalanche hdPathCoinType to 60 so imported seeds yield a matching
address. Existing wallets keep their cached public key (no checkPublicKey hook),
so their addresses and funds are unaffected; only newly created or imported
avalanche wallets adopt the corrected path.

Add a regression test asserting the avalanche plugin derives the known EVM
address from an imported seed.
@j0ntz

j0ntz commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

📸 In-app bug reproduction (pre-fix state). Importing the 12-word EVM seed into Edge derives the AVAX C-Chain wallet at coin type 9000, producing 0xc0Ee5411B61513Bea1853692463e28D6c32A0b50, which does NOT match the source wallet (Exodus). This PR changes AVAX to coin type 60 (the same path ETH and POL already use, which is why those match), so the identical seed derives the matching 0x21D45Fd06e291C49AbFa135460DE827b6579Cef5. Proven deterministically by test/ethereum/avalancheDerivation.test.ts.

AVAX receive PRE FIX derives coinType 9000 address does not match Exodus

AVAX receive PRE FIX derives coinType 9000 address does not match Exodus

Captured by the agent's in-app test run (build-and-test).

@j0ntz

j0ntz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Corrected test evidence

This replaces the earlier proof screenshots, which were iOS springboard (home screen) frames captured from a simulator the maestro daemon was not actually driving. The fix itself is unchanged.

Genuine re-test on the iOS simulator (slot UDID 44B54874-D8A9-4A36-8EAB-1900D96FD722): imported the affected seed room soda device label bicycle hill fork nest lion knee purpose hen into a fresh AVAX wallet with this PR's coin-type-60 plugin loaded (DEBUG_ACCOUNTBASED=true + the fix branch served on :8082). The receive scene shows:

0x21D45Fd06e291C49AbFa135460DE827b6579Cef5

That is the coin-type-60 address Exodus, MetaMask, and Trust derive, and the value asserted by this PR's avalancheDerivation.test.ts.

Control on the same seed under the pre-fix coin-type-9000 build: 0xc0Ee5411B61513Bea1853692463e28D6c32A0b50. Offline derivation confirms 60 yields 0x21D4..Cef5 and 9000 yields 0xc0Ee..0b50, so the two on-screen addresses are exactly the buggy vs fixed derivations of one seed.

Screenshots below were captured via simctl io against the exact simulator UDID that was driven.

@j0ntz

j0ntz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

📸 Genuine in-app AVAX receive proof (re-test)

imported seed fixed coin type 60 0x21D45Fd06e291C49AbFa135460DE827b6579Cef5

imported seed fixed coin type 60 0x21D45Fd06e291C49AbFa135460DE827b6579Cef5

same seed pre fix coin type 9000 0xc0Ee5411B61513Bea1853692463e28D6c32A0b50

same seed pre fix coin type 9000 0xc0Ee5411B61513Bea1853692463e28D6c32A0b50

Captured by the agent's in-app test run (build-and-test).

hdPathCoinType: 9000,
// AVAX C-Chain is EVM and standard wallets (Exodus, MetaMask, Trust) derive it
// at Ethereum's coin type so imported seeds yield a matching receive address.
hdPathCoinType: 60,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is an incomplete fix. Existing wallets opened on a new device will be derive an address at the new path. One way to resolve is to save the path when creating the wallet and have derive pubkey use it if it's present or fallback to another value if it isn't. I believe we've done this for polkadot tools or another currency tools in the past.

Another solution could be to finally support passing in the path as an optional import option. That would require we save the path in the keys object.

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.

2 participants