Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e384b27
Feature - Integrate MoonPay SDK for buy/sell functionality in web app…
Corey-Code Feb 1, 2026
4c993b0
feat: add BIP32 and BIP84 derivation tests for Bitcoin addresses
Corey-Code Feb 2, 2026
71c0843
feat: implement IBC transfer functionality and modal in the dashboard
Corey-Code Feb 3, 2026
d47d4dc
Add fee validation for sweepAll transactions to prevent excessive fee…
Copilot Feb 3, 2026
969bac5
Fix memory safety in deriveBitcoinKeyPairFromSeed key cleanup (#55)
Copilot Feb 3, 2026
76e8856
Secure pubKey cleanup in BIP32 child key derivation (#54)
Copilot Feb 3, 2026
e809bb9
Update transaction.ts
Corey-Code Feb 3, 2026
2e56d5d
Update bitcoin.ts
Corey-Code Feb 3, 2026
9fc6cbf
Update encrypted-storage.ts
Corey-Code Feb 3, 2026
7f3cc59
Update transaction.test.ts
Corey-Code Feb 3, 2026
06d301a
Update walletStore.ts
Corey-Code Feb 3, 2026
fa968f2
Update ibc-connections.ts
Corey-Code Feb 3, 2026
fc087ac
Update IBCTransferModal.tsx
Corey-Code Feb 3, 2026
cab8e54
Update IBCTransferModal.tsx
Corey-Code Feb 3, 2026
009f593
Update IBCTransferModal.tsx
Corey-Code Feb 3, 2026
8bfb419
Update IBCTransferModal.tsx
Corey-Code Feb 3, 2026
98581d8
Update Dashboard.tsx
Corey-Code Feb 3, 2026
3bef223
Update chainRegistry.test.ts
Corey-Code Feb 3, 2026
7cddb2f
Update transaction.test.ts
Corey-Code Feb 3, 2026
4e198da
Simplify secureZero function in evm.ts to remove ineffective random o…
Copilot Feb 3, 2026
1efb230
Add BIP32 hardened derivation indicator documentation (#57)
Copilot Feb 3, 2026
8533d63
Fix memory leak in EVM BIP32 child key derivation (#58)
Copilot Feb 3, 2026
9621ce4
Use actual UTXO count for Bitcoin max amount fee estimation (#59)
Copilot Feb 3, 2026
314ff38
Remove forceReDerive parameter from address derivation (#60)
Copilot Feb 3, 2026
09ff6a6
Refactor IBC connections to use pre-bundled data; remove runtime fetc…
Corey-Code Feb 3, 2026
f5c353c
Optimize address caching with two-level structure to persist across n…
Copilot Feb 3, 2026
b6b1f83
Add runtime Buffer polyfill checks to prevent initialization order is…
Copilot Feb 3, 2026
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
14 changes: 14 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 @@ -33,6 +33,7 @@
"@cosmjs/stargate": "^0.32.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@moonpay/moonpay-react": "^1.10.6",
"@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