Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

---

Expand All @@ -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
Expand All @@ -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 |
Expand Down
10 changes: 10 additions & 0 deletions megaeth.testnet.tokenlist.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "MegaETH Testnet Token List",
"timestamp": "2026-04-22T00:58:21.755Z",
"version": {
"major": 1,
"minor": 0,
"patch": 0
},
"tokens": []
}
2 changes: 1 addition & 1 deletion megaeth.tokenlist.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
47 changes: 35 additions & 12 deletions src/__tests__/generate.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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');
Expand All @@ -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');
Expand Down Expand Up @@ -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();
}
});

Expand All @@ -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)$/
);
}
});
Expand All @@ -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);
});
});
26 changes: 24 additions & 2 deletions src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EvmChain, number> = {
ethereum: 1,
megaeth: 4326,
megaeth_testnet: 6343,
}

// Source chains (non-EVM) - used for tracking bridged asset origins
Expand All @@ -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<L2Chain, L1Chain> = {
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'],
}
31 changes: 23 additions & 8 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<TokenListTarget, string> = {
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'

Expand Down Expand Up @@ -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)
Expand All @@ -114,6 +119,7 @@ export function generate(): TokenList {
.sort()

const tokens: TokenListToken[] = []
const includedChains = new Set<EvmChain>(TOKENLIST_TARGET_CHAINS[target])

for (const symbol of tokenDirs) {
const tokenDir = path.join(DATA_DIR, symbol)
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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`
)
}
}
Loading