diff --git a/CHANGELOG.md b/CHANGELOG.md index d46b2c7de..995411e0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fixed: (Avalanche) Derive C-Chain addresses at coin type 60 so seeds imported from EVM wallets (Exodus, MetaMask, Trust) produce a matching receive address. Existing wallets keep their cached address and are unaffected. + ## 4.82.1 (2026-05-27) - changed: (FIO) Replace forked `@fioprotocol/fiosdk` with official npm 1.10.3. diff --git a/src/ethereum/info/avalancheInfo.ts b/src/ethereum/info/avalancheInfo.ts index 98d2ecd0f..8c4f5b3b4 100644 --- a/src/ethereum/info/avalancheInfo.ts +++ b/src/ethereum/info/avalancheInfo.ts @@ -212,7 +212,9 @@ const networkInfo: EthereumNetworkInfo = { name: 'AVAX Mainnet' }, supportsEIP1559: true, - 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, pluginMnemonicKeyName: 'avalancheMnemonic', pluginRegularKeyName: 'avalancheKey', evmGasStationUrl: null, diff --git a/test/ethereum/avalancheDerivation.test.ts b/test/ethereum/avalancheDerivation.test.ts new file mode 100644 index 000000000..6259da635 --- /dev/null +++ b/test/ethereum/avalancheDerivation.test.ts @@ -0,0 +1,63 @@ +import { assert } from 'chai' +import { + EdgeCorePluginOptions, + EdgeCurrencyPlugin, + EdgeCurrencyTools, + makeFakeIo +} from 'edge-core-js' +import { before, describe, it } from 'mocha' +import fetch from 'node-fetch' + +import edgeCorePlugins from '../../src/index' +import { fakeLog } from '../fake/fakeLog' + +// A 12-word seed and the receive address it yields on EVM wallets (Exodus, +// MetaMask, Trust) which all derive at coin type 60. Ethereum, Polygon and +// Avalanche C-Chain are all EVM, so importing this seed into Edge must produce +// the same address on each. Avalanche was the regression (it derived at coin +// type 9000); Ethereum and Polygon are the chains the report flagged as most +// important, so they are locked here too. +const MNEMONIC = + 'room soda device label bicycle hill fork nest lion knee purpose hen' +const EXPECTED_EVM_ADDRESS = '0x21D45Fd06e291C49AbFa135460DE827b6579Cef5' + +const makeOpts = (): EdgeCorePluginOptions => { + const fakeIo = makeFakeIo() + return { + infoPayload: {}, + initOptions: {}, + io: { ...fakeIo, fetch, fetchCors: fetch }, + log: fakeLog, + nativeIo: {}, + pluginDisklet: fakeIo.disklet + } +} + +const CASES = [ + { pluginId: 'ethereum', mnemonicKey: 'ethereumMnemonic' }, + { pluginId: 'polygon', mnemonicKey: 'polygonMnemonic' }, + { pluginId: 'avalanche', mnemonicKey: 'avalancheMnemonic' } +] as const + +describe('EVM derivation parity (coin type 60)', function () { + for (const { pluginId, mnemonicKey } of CASES) { + describe(pluginId, function () { + let tools: EdgeCurrencyTools + + before('Tools', async function () { + const factory = edgeCorePlugins[pluginId] + const plugin: EdgeCurrencyPlugin = factory(makeOpts()) + tools = await plugin.makeCurrencyTools() + }) + + it('derives the Exodus-matching EVM address from an imported seed', async function () { + const keys = await tools.derivePublicKey({ + id: 'id', + keys: { [mnemonicKey]: MNEMONIC }, + type: `wallet:${pluginId}` + }) + assert.equal(keys.publicKey, EXPECTED_EVM_ADDRESS) + }) + }) + } +})