Multi-Chain dApp Architecture Guide
This comprehensive guide covers building robust multi-chain decentralized applications that can interact with multiple blockchains simultaneously, providing users with a seamless cross-chain experience.
Architecture Overview
Multi-chain dApps face unique challenges including:
- State Management: Coordinating data across different blockchains
- Transaction Coordination: Managing operations across multiple networks
- Error Handling: Dealing with varying failure modes
- User Experience: Providing unified interface for different chains
- Performance: Optimizing for multiple network latencies
Core Architecture Patterns
1. Chain Abstraction Layer
// types/blockchain.ts
export enum SupportedChain {
SUI = 'sui',
APTOS = 'aptos',
ETHEREUM = 'ethereum',
POLYGON = 'polygon',
}
export interface ChainConfig {
chainId: string;
name: string;
rpcUrl: string;
nativeCurrency: {
name: string;
symbol: string;
decimals: number;
};
blockExplorer?: string;
}
export interface BlockchainClient {
getBalance(address: string): Promise<string>;
sendTransaction(tx: any): Promise<string>;
getTransactionStatus(hash: string): Promise<TransactionStatus>;
estimateGas?(tx: any): Promise<string>;
}
export interface TransactionStatus {
hash: string;
status: 'pending' | 'success' | 'failed';
blockNumber?: number;
gasUsed?: string;
}
2. Unified Client Manager
// clients/ChainManager.ts
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
import { Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk';
import { SupportedChain, ChainConfig, BlockchainClient } from '../types/blockchain';
export class ChainManager {
private clients: Map<SupportedChain, BlockchainClient> = new Map();
private configs: Map<SupportedChain, ChainConfig> = new Map();
constructor() {
this.initializeChains();
}
private initializeChains() {
// Initialize Sui
const suiConfig: ChainConfig = {
chainId: 'sui-mainnet',
name: 'Sui Mainnet',
rpcUrl: process.env.NEXT_PUBLIC_SUI_RPC_URL || getFullnodeUrl('mainnet'),
nativeCurrency: { name: 'SUI', symbol: 'SUI', decimals: 9 },
blockExplorer: 'https://suiscan.xyz',
};
this.configs.set(SupportedChain.SUI, suiConfig);
this.clients.set(SupportedChain.SUI, new SuiClientAdapter(suiConfig.rpcUrl));
// Initialize Aptos
const aptosConfig: ChainConfig = {
chainId: 'aptos-mainnet',
name: 'Aptos Mainnet',
rpcUrl: process.env.NEXT_PUBLIC_APTOS_RPC_URL || 'https://fullnode.mainnet.aptoslabs.com/v1',
nativeCurrency: { name: 'APT', symbol: 'APT', decimals: 8 },
blockExplorer: 'https://aptoscan.com',
};
this.configs.set(SupportedChain.APTOS, aptosConfig);
this.clients.set(SupportedChain.APTOS, new AptosClientAdapter(aptosConfig.rpcUrl));
}
getClient(chain: SupportedChain): BlockchainClient {
const client = this.clients.get(chain);
if (!client) {
throw new Error(`Client not initialized for chain: ${chain}`);
}
return client;
}
getConfig(chain: SupportedChain): ChainConfig {
const config = this.configs.get(chain);
if (!config) {
throw new Error(`Config not found for chain: ${chain}`);
}
return config;
}
getSupportedChains(): SupportedChain[] {
return Array.from(this.clients.keys());
}
async getBalanceAcrossChains(address: string): Promise<Record<SupportedChain, string>> {
const balances: Record<string, string> = {};
const promises = Array.from(this.clients.entries()).map(async ([chain, client]) => {
try {
const balance = await client.getBalance(address);
balances[chain] = balance;
} catch (error) {
console.error(`Error fetching balance for ${chain}:`, error);
balances[chain] = '0';
}
});
await Promise.allSettled(promises);
return balances as Record<SupportedChain, string>;
}
}