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
1,383 changes: 1,383 additions & 0 deletions docs/ADDING_NETWORK_TYPE.md

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@moonpay/moonpay-react": "^1.10.6",
"@noble/curves": "^2.0.1",
"@noble/hashes": "^1.3.3",
"@noble/secp256k1": "^2.0.0",
"bip32": "^4.0.0",
Expand Down
220 changes: 216 additions & 4 deletions scripts/sync-chain-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,53 @@ interface WalletAssetConfig {
type?: 'native' | 'ibc' | 'cw20' | 'factory';
}

/**
* IBC Channel info for cross-chain transfers
*/
interface IBCChannelConfig {
sourceChainId: string;
sourceChainName: string;
sourceChannelId: string;
sourcePort: string;
destChainId: string;
destChainName: string;
destChannelId: string;
destPort: string;
status: 'ACTIVE' | 'INACTIVE' | 'UNKNOWN';
}

/**
* Raw IBC data from chain registry (_IBC/*.json)
*/
interface ChainRegistryIBCData {
chain_1: {
chain_name: string;
client_id: string;
connection_id: string;
};
chain_2: {
chain_name: string;
client_id: string;
connection_id: string;
};
channels: Array<{
chain_1: {
channel_id: string;
port_id: string;
};
chain_2: {
channel_id: string;
port_id: string;
};
ordering: string;
version: string;
tags?: {
preferred?: boolean;
status?: string;
};
}>;
}

/**
* Fetch JSON from URL with error handling
*/
Expand Down Expand Up @@ -224,13 +271,14 @@ function transformChain(chain: ChainRegistryChain): WalletChainConfig | null {
// Extract relative paths from explorer URLs if they are absolute
let explorerAccountPath = explorer?.account_page?.replace('${accountAddress}', '{address}');
let explorerTxPath = explorer?.tx_page?.replace('${txHash}', '{txHash}');

// If the paths are absolute URLs and match the base explorerUrl, extract the relative part
// This standardizes configs to use relative paths for consistency
// If the absolute URL has a different domain than explorerUrl, leave it as-is (the helper
// functions in registry.ts will detect it's absolute and return it directly)
if (explorer?.url && explorerAccountPath) {
const isAbsoluteUrl = explorerAccountPath.startsWith('http://') || explorerAccountPath.startsWith('https://');
const isAbsoluteUrl =
explorerAccountPath.startsWith('http://') || explorerAccountPath.startsWith('https://');
if (isAbsoluteUrl) {
const baseUrl = explorer.url.replace(/\/$/, ''); // Remove trailing slash
if (explorerAccountPath.startsWith(baseUrl)) {
Expand All @@ -239,9 +287,10 @@ function transformChain(chain: ChainRegistryChain): WalletChainConfig | null {
// else: Different domain - leave as absolute URL, will be handled by helper functions
}
}

if (explorer?.url && explorerTxPath) {
const isAbsoluteUrl = explorerTxPath.startsWith('http://') || explorerTxPath.startsWith('https://');
const isAbsoluteUrl =
explorerTxPath.startsWith('http://') || explorerTxPath.startsWith('https://');
if (isAbsoluteUrl) {
const baseUrl = explorer.url.replace(/\/$/, '');
if (explorerTxPath.startsWith(baseUrl)) {
Expand Down Expand Up @@ -424,6 +473,157 @@ export function getNativeAsset(chainName: string): RegistryAssetConfig | undefin
`;
}

/**
* Generate TypeScript code for IBC channels
*/
function generateIBCCode(ibcChannels: IBCChannelConfig[]): string {
const ibcJson = JSON.stringify(ibcChannels, null, 2)
.replace(/"([^"]+)":/g, '$1:')
.replace(/"/g, "'");

return `/**
* Cosmos IBC Channel Registry - Auto-generated
*
* This file is generated by scripts/sync-chain-registry.ts
* Source: https://github.com/cosmos/chain-registry/_IBC
*
* DO NOT EDIT MANUALLY - Run \`npm run sync:chains\` to update
*
* Generated: ${new Date().toISOString()}
*/

/**
* IBC Channel info for cross-chain transfers
*/
export interface IBCChannelConfig {
sourceChainId: string;
sourceChainName: string;
sourceChannelId: string;
sourcePort: string;
destChainId: string;
destChainName: string;
destChannelId: string;
destPort: string;
status: 'ACTIVE' | 'INACTIVE' | 'UNKNOWN';
}

/**
* Pre-bundled IBC channels between enabled chains
*/
export const IBC_CHANNELS: IBCChannelConfig[] = ${ibcJson};

/**
* Get IBC channels for a source chain
*/
export function getIBCChannelsForChain(sourceChainName: string): IBCChannelConfig[] {
return IBC_CHANNELS.filter(c => c.sourceChainName === sourceChainName);
}

/**
* Get IBC channel between two chains
*/
export function getIBCChannel(sourceChainName: string, destChainName: string): IBCChannelConfig | undefined {
return IBC_CHANNELS.find(
c => c.sourceChainName === sourceChainName && c.destChainName === destChainName
);
}

/**
* Get IBC channels by chain ID
*/
export function getIBCChannelsForChainId(sourceChainId: string): IBCChannelConfig[] {
return IBC_CHANNELS.filter(c => c.sourceChainId === sourceChainId);
}
`;
}

/**
* Fetch IBC connections between chains
*/
async function fetchIBCConnections(
chains: WalletChainConfig[],
chainIdMap: Map<string, string>
): Promise<IBCChannelConfig[]> {
const ibcChannels: IBCChannelConfig[] = [];
const chainNames = chains.map((c) => c.chainName);
const fetchedPairs = new Set<string>();

console.log('\n🔗 Fetching IBC connections...\n');

for (let i = 0; i < chainNames.length; i++) {
for (let j = i + 1; j < chainNames.length; j++) {
const chain1 = chainNames[i];
const chain2 = chainNames[j];

// IBC files are named alphabetically
const names = [chain1, chain2].sort();
const pairKey = `${names[0]}-${names[1]}`;

if (fetchedPairs.has(pairKey)) continue;
fetchedPairs.add(pairKey);

const ibcFileName = `${pairKey}.json`;
const url = `${CHAIN_REGISTRY_BASE}/_IBC/${ibcFileName}`;

const ibcData = await fetchJson<ChainRegistryIBCData>(url);
if (!ibcData) continue;

// Find the preferred/active transfer channel
const transferChannel =
ibcData.channels.find(
(ch) =>
ch.chain_1.port_id === 'transfer' &&
ch.chain_2.port_id === 'transfer' &&
ch.tags?.status === 'live'
) ||
ibcData.channels.find(
(ch) => ch.chain_1.port_id === 'transfer' && ch.chain_2.port_id === 'transfer'
);

if (!transferChannel) continue;

// Get chain IDs from our map
const chain1Id = chainIdMap.get(ibcData.chain_1.chain_name);
const chain2Id = chainIdMap.get(ibcData.chain_2.chain_name);

if (!chain1Id || !chain2Id) continue;

// Add both directions
ibcChannels.push({
sourceChainId: chain1Id,
sourceChainName: ibcData.chain_1.chain_name,
sourceChannelId: transferChannel.chain_1.channel_id,
sourcePort: transferChannel.chain_1.port_id,
destChainId: chain2Id,
destChainName: ibcData.chain_2.chain_name,
destChannelId: transferChannel.chain_2.channel_id,
destPort: transferChannel.chain_2.port_id,
status: (transferChannel.tags?.status === 'live'
? 'ACTIVE'
: 'UNKNOWN') as IBCChannelConfig['status'],
});

ibcChannels.push({
sourceChainId: chain2Id,
sourceChainName: ibcData.chain_2.chain_name,
sourceChannelId: transferChannel.chain_2.channel_id,
sourcePort: transferChannel.chain_2.port_id,
destChainId: chain1Id,
destChainName: ibcData.chain_1.chain_name,
destChannelId: transferChannel.chain_1.channel_id,
destPort: transferChannel.chain_1.port_id,
status: (transferChannel.tags?.status === 'live'
? 'ACTIVE'
: 'UNKNOWN') as IBCChannelConfig['status'],
});

process.stdout.write(` ${pairKey} ✅\n`);
}
}

return ibcChannels;
}

/**
* Main sync function
*/
Expand Down Expand Up @@ -471,9 +671,18 @@ async function syncChainRegistry(chainNames: string[] = DEFAULT_CHAINS) {
`\n📊 Summary: ${chains.length} chains, ${[...assetsByChain.values()].flat().length} assets\n`
);

// Build chain name -> chain ID map for IBC fetching
const chainIdMap = new Map<string, string>();
chains.forEach((c) => chainIdMap.set(c.chainName, c.id));

// Fetch IBC connections between chains
const ibcChannels = await fetchIBCConnections(chains, chainIdMap);
console.log(`\n📊 IBC Summary: ${ibcChannels.length} channel directions\n`);

// Generate code
const chainsCode = generateChainsCode(chains);
const assetsCode = generateAssetsCode(assetsByChain);
const ibcCode = generateIBCCode(ibcChannels);

// Write files
const srcDir = path.join(process.cwd(), 'src/lib');
Expand All @@ -484,6 +693,9 @@ async function syncChainRegistry(chainNames: string[] = DEFAULT_CHAINS) {
await fs.writeFile(path.join(srcDir, 'assets/cosmos-registry.ts'), assetsCode, 'utf-8');
console.log('✅ Generated src/lib/assets/cosmos-registry.ts');

await fs.writeFile(path.join(srcDir, 'assets/ibc-registry.ts'), ibcCode, 'utf-8');
console.log('✅ Generated src/lib/assets/ibc-registry.ts');

console.log('\n🎉 Chain registry sync complete!');
}

Expand Down
Loading