diff --git a/README.md b/README.md index e5988c2..717e88b 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ # MegaETH Token List -The official token registry for the MegaETH ecosystem. This repository maintains a curated list of tokens deployed on MegaETH and their corresponding addresses on other chains for bridging and cross-chain tracking. +The official token registry for the MegaETH ecosystem. This repository maintains curated token lists for MegaETH and their corresponding addresses on other chains for bridging and cross-chain tracking. -The generated tokenlist follows the [Uniswap Token List](https://github.com/Uniswap/token-lists) standard with MegaETH-specific extensions for tracking bridge mechanics and token origins. +The generated tokenlists follow the [Uniswap Token List](https://github.com/Uniswap/token-lists) standard with MegaETH-specific extensions for tracking bridge mechanics and token origins. ## Supported Chains -| Chain | Chain ID | Type | Description | -| -------- | -------- | ------ | --------------------------------------- | -| Ethereum | 1 | L1 | Ethereum mainnet | -| MegaETH | 4326 | L2 | MegaETH mainnet | -| Solana | - | Source | Non-EVM source chain for bridged assets | +| Chain | Chain ID | Type | Description | +| --------------- | -------- | ------ | --------------------------------------- | +| Ethereum | 1 | L1 | Ethereum mainnet | +| MegaETH | 4326 | L2 | MegaETH mainnet | +| MegaETH Testnet | 6343 | L2 | MegaETH testnet | +| Solana | - | Source | Non-EVM source chain for bridged assets | -> **Note:** Only EVM chains (Ethereum, MegaETH) appear in the generated tokenlist. Non-EVM chains like Solana are tracked as source chains — their addresses appear in the `extensions` field. +> **Note:** This repo produces separate outputs for mainnet and testnet. The mainnet tokenlist includes only Ethereum + MegaETH mainnet entries. Testnet tokens are emitted only in the separate testnet list to avoid contaminating production consumers. --- @@ -25,6 +26,11 @@ The generated tokenlist follows the [Uniswap Token List](https://github.com/Unis 3. Add `logo.svg` or `logo.png` (256×256 recommended) 4. Submit a PR +### Generated Outputs + +- `megaeth.tokenlist.json` — mainnet list (Ethereum + MegaETH mainnet) +- `megaeth.testnet.tokenlist.json` — testnet list (MegaETH testnet only) + --- ## Token Data Schema @@ -41,6 +47,15 @@ The generated tokenlist follows the [Uniswap Token List](https://github.com/Unis ### Per-Chain Fields +Each chain entry in `tokens` supports. + +Supported keys today: + +- `ethereum` +- `megaeth` +- `megaeth_testnet` +- `solana` (source tracking only, not emitted directly into tokenlists) + Each chain entry in `tokens` supports: | Field | Type | Description | diff --git a/megaeth.testnet.tokenlist.json b/megaeth.testnet.tokenlist.json new file mode 100644 index 0000000..77a3e08 --- /dev/null +++ b/megaeth.testnet.tokenlist.json @@ -0,0 +1,10 @@ +{ + "name": "MegaETH Testnet Token List", + "timestamp": "2026-04-22T00:58:21.755Z", + "version": { + "major": 1, + "minor": 0, + "patch": 0 + }, + "tokens": [] +} diff --git a/megaeth.tokenlist.json b/megaeth.tokenlist.json index ad834bd..c4edc0f 100644 --- a/megaeth.tokenlist.json +++ b/megaeth.tokenlist.json @@ -1,6 +1,6 @@ { "name": "MegaETH Token List", - "timestamp": "2026-02-24T01:41:27.206Z", + "timestamp": "2026-04-22T00:58:21.752Z", "version": { "major": 1, "minor": 0, diff --git a/src/__tests__/generate.test.ts b/src/__tests__/generate.test.ts index be07497..9d6a344 100644 --- a/src/__tests__/generate.test.ts +++ b/src/__tests__/generate.test.ts @@ -1,4 +1,4 @@ -import { CHAINS, CHAIN_IDS, L2_TO_L1 } from '../chains'; +import { CHAINS, CHAIN_IDS, L2_TO_L1, TOKENLIST_TARGET_CHAINS } from '../chains'; import { generate } from '../generate'; describe('Chain Configuration', () => { @@ -15,10 +15,21 @@ describe('Chain Configuration', () => { test('MegaETH L2 maps to Ethereum L1', () => { expect(L2_TO_L1.megaeth).toBe('ethereum'); }); + + test('MegaETH testnet chain ID is 6343', () => { + expect(CHAIN_IDS.megaeth_testnet).toBe(6343); + expect(CHAINS.megaeth_testnet.id).toBe(6343); + }); + + test('tokenlist targets separate mainnet and testnet chains', () => { + expect(TOKENLIST_TARGET_CHAINS.mainnet).toEqual(['ethereum', 'megaeth']); + expect(TOKENLIST_TARGET_CHAINS.testnet).toEqual(['megaeth_testnet']); + }); }); describe('Token List Generation', () => { const tokenList = generate(); + const testnetTokenList = generate('testnet'); test('generates valid token list structure', () => { expect(tokenList).toHaveProperty('name', 'MegaETH Token List'); @@ -28,6 +39,14 @@ describe('Token List Generation', () => { expect(Array.isArray(tokenList.tokens)).toBe(true); }); + test('generates separate testnet token list structure', () => { + expect(testnetTokenList).toHaveProperty('name', 'MegaETH Testnet Token List'); + expect(testnetTokenList).toHaveProperty('timestamp'); + expect(testnetTokenList).toHaveProperty('version'); + expect(testnetTokenList).toHaveProperty('tokens'); + expect(Array.isArray(testnetTokenList.tokens)).toBe(true); + }); + test('version has correct structure', () => { expect(tokenList.version).toHaveProperty('major'); expect(tokenList.version).toHaveProperty('minor'); @@ -58,14 +77,12 @@ describe('Token List Generation', () => { } }); - test('native tokens have isOrigin: true', () => { - const nativeTokens = tokenList.tokens.filter((t) => t.extensions.isOrigin); - expect(nativeTokens.length).toBeGreaterThan(0); + test('origin tokens have isOrigin: true', () => { + const originTokens = tokenList.tokens.filter((t) => t.extensions.isOrigin); + expect(originTokens.length).toBeGreaterThan(0); - for (const token of nativeTokens) { + for (const token of originTokens) { expect(token.extensions.isOrigin).toBe(true); - expect(token.extensions.bridgeAddress).toBeUndefined(); - expect(token.extensions.bridgeType).toBeUndefined(); } }); @@ -74,7 +91,7 @@ describe('Token List Generation', () => { for (const token of tokensWithLogos) { expect(token.logoURI).toMatch( - /^https:\/\/raw\.githubusercontent\.com\/megaeth-labs\/mega-tokenlist\/main\/data\/\w+\/logo\.(svg|png)$/ + /^https:\/\/raw\.githubusercontent\.com\/megaeth-labs\/mega-tokenlist\/main\/data\/.+\/logo\.(svg|png)$/ ); } }); @@ -87,10 +104,16 @@ describe('Token List Generation', () => { expect(chainIds).toEqual([1, 4326]); }); - test('WETH token exists only on MegaETH', () => { + test('WETH token includes a MegaETH mainnet entry', () => { const wethTokens = tokenList.tokens.filter((t) => t.symbol === 'WETH'); - expect(wethTokens.length).toBe(1); - expect(wethTokens[0].chainId).toBe(4326); - expect(wethTokens[0].extensions.isOrigin).toBe(true); + expect(wethTokens.some((t) => t.chainId === 4326)).toBe(true); + }); + + test('mainnet token list excludes testnet chain ids', () => { + expect(tokenList.tokens.every((t) => t.chainId !== 6343)).toBe(true); + }); + + test('testnet token list only contains testnet chain ids', () => { + expect(testnetTokenList.tokens.every((t) => t.chainId === 6343)).toBe(true); }); }); diff --git a/src/chains.ts b/src/chains.ts index ec09b7e..ab01130 100644 --- a/src/chains.ts +++ b/src/chains.ts @@ -6,31 +6,42 @@ export const CHAINS = { name: 'Ethereum', layer: 1, evmCompatible: true, + environment: 'mainnet', }, megaeth: { id: 4326, name: 'MegaETH', layer: 2, evmCompatible: true, + environment: 'mainnet', + }, + megaeth_testnet: { + id: 6343, + name: 'MegaETH Testnet', + layer: 2, + evmCompatible: true, + environment: 'testnet', }, solana: { id: null, // Non-EVM chain, uses string identifier name: 'Solana', layer: 1, evmCompatible: false, + environment: 'mainnet', }, } as const export type Chain = keyof typeof CHAINS -export type EvmChain = 'ethereum' | 'megaeth' +export type EvmChain = 'ethereum' | 'megaeth' | 'megaeth_testnet' export type SourceChain = 'solana' // Non-EVM chains used only for source tracking export type L1Chain = 'ethereum' -export type L2Chain = 'megaeth' +export type L2Chain = 'megaeth' | 'megaeth_testnet' // Chain ID lookup (EVM chains only - used for tokenlist generation) export const CHAIN_IDS: Record = { ethereum: 1, megaeth: 4326, + megaeth_testnet: 6343, } // Source chains (non-EVM) - used for tracking bridged asset origins @@ -39,4 +50,15 @@ export const SOURCE_CHAINS: readonly SourceChain[] = ['solana'] as const // L2 to L1 mapping (for bridge relationships) export const L2_TO_L1: Record = { megaeth: 'ethereum', + megaeth_testnet: 'ethereum', +} + +export type TokenListTarget = 'mainnet' | 'testnet' + +export const TOKENLIST_TARGET_CHAINS: Record< + TokenListTarget, + readonly EvmChain[] +> = { + mainnet: ['ethereum', 'megaeth'], + testnet: ['megaeth_testnet'], } diff --git a/src/generate.ts b/src/generate.ts index 2bf5e22..6654646 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -3,8 +3,10 @@ import * as path from 'path' import { CHAIN_IDS, SOURCE_CHAINS, + TOKENLIST_TARGET_CHAINS, type EvmChain, type SourceChain, + type TokenListTarget, } from './chains' import type { Mechanism, @@ -15,7 +17,10 @@ import type { } from './types' const DATA_DIR = path.join(__dirname, '..', 'data') -const OUTPUT_FILE = path.join(__dirname, '..', 'megaeth.tokenlist.json') +const OUTPUT_FILES: Record = { + mainnet: path.join(__dirname, '..', 'megaeth.tokenlist.json'), + testnet: path.join(__dirname, '..', 'megaeth.testnet.tokenlist.json'), +} const LOGO_BASE_URL = 'https://raw.githubusercontent.com/megaeth-labs/mega-tokenlist/main/data' @@ -103,7 +108,7 @@ function inferMechanism( return 'unknown' } -export function generate(): TokenList { +export function generate(target: TokenListTarget = 'mainnet'): TokenList { // Read all token directories const tokenDirs = fs .readdirSync(DATA_DIR) @@ -114,6 +119,7 @@ export function generate(): TokenList { .sort() const tokens: TokenListToken[] = [] + const includedChains = new Set(TOKENLIST_TARGET_CHAINS[target]) for (const symbol of tokenDirs) { const tokenDir = path.join(DATA_DIR, symbol) @@ -126,8 +132,9 @@ export function generate(): TokenList { for (const [chain, chainToken] of Object.entries(tokenData.tokens)) { if (!chainToken?.address) continue - const chainId = CHAIN_IDS[chain as EvmChain] - if (!chainId) continue + const evmChain = chain as EvmChain + const chainId = CHAIN_IDS[evmChain] + if (!chainId || !includedChains.has(evmChain)) continue const isOrigin = chainToken.isOrigin === true const mechanism = inferMechanism(chainToken, isOrigin) @@ -189,7 +196,10 @@ export function generate(): TokenList { }) const tokenList: TokenList = { - name: 'MegaETH Token List', + name: + target === 'mainnet' + ? 'MegaETH Token List' + : 'MegaETH Testnet Token List', timestamp: new Date().toISOString(), version: { major: 1, @@ -204,7 +214,12 @@ export function generate(): TokenList { // Main execution if (require.main === module) { - const tokenList = generate() - fs.writeFileSync(OUTPUT_FILE, JSON.stringify(tokenList, null, 2)) - console.log(`Generated ${OUTPUT_FILE} with ${tokenList.tokens.length} tokens`) + for (const target of ['mainnet', 'testnet'] as const) { + const tokenList = generate(target) + const outputFile = OUTPUT_FILES[target] + fs.writeFileSync(outputFile, JSON.stringify(tokenList, null, 2)) + console.log( + `Generated ${outputFile} with ${tokenList.tokens.length} tokens` + ) + } }