Multichain
Multichain
#Make Multichain dApps with Magic
Modern blockchain applications increasingly need to support multiple networks, including both mainnet and testnet environments across different blockchain ecosystems. Magic's SDK makes this possible by allowing you to create separate instances for each blockchain you want to support.
#Setting Up Network Constants
First, define an enum for all supported networks and helper functions to retrieve network-specific information:
01export enum Network {
02 POLYGON_AMOY = 'polygon-amoy',
03 POLYGON = 'polygon',
04 ETHEREUM_SEPOLIA = 'ethereum-sepolia',
05 ETHEREUM = 'ethereum',
06 ETHERLINK = 'etherlink',
07 ETHERLINK_TESTNET = 'etherlink-testnet',
08 ZKSYNC = 'zksync',
09 ZKSYNC_SEPOLIA = 'zksync-sepolia',
10}
11
12// Get RPC URL for the current network
13export const getNetworkUrl = (network: Network): string => {
14 switch (network) {
15 case Network.POLYGON:
16 return 'https://polygon-rpc.com/';
17 case Network.POLYGON_AMOY:
18 return 'https://rpc-amoy.polygon.technology/';
19 case Network.ETHEREUM_SEPOLIA:
20 return 'https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY';
21 case Network.ETHEREUM:
22 return 'https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY';
23 case Network.ETHERLINK:
24 return 'https://node.mainnet.etherlink.com';
25 case Network.ETHERLINK_TESTNET:
26 return 'https://node.ghostnet.etherlink.com';
27 case Network.ZKSYNC:
28 return 'https://mainnet.era.zksync.io';
29 case Network.ZKSYNC_SEPOLIA:
30 return 'https://zksync-era-sepolia.blockpi.network/v1/rpc/public';
31 default:
32 throw new Error('Network not supported');
33 }
34};
35
36// Get chain ID for the current network
37export const getChainId = (network: Network): number => {
38 switch (network) {
39 case Network.POLYGON:
40 return 137;
41 case Network.POLYGON_AMOY:
42 return 80002;
43 case Network.ETHEREUM_SEPOLIA:
44 return 11155111;
45 case Network.ETHEREUM:
46 return 1;
47 case Network.ETHERLINK:
48 return 42793;
49 case Network.ETHERLINK_TESTNET:
50 return 128123;
51 case Network.ZKSYNC:
52 return 324;
53 case Network.ZKSYNC_SEPOLIA:
54 return 300;
55 default:
56 throw new Error('Network not supported');
57 }
58};
#Creating Magic Instances for Multiple Networks
With the network utilities in place, you can create Magic instances for each supported network:
01import { Magic } from 'magic-sdk';
02import { Network, getNetworkUrl, getChainId } from './networkUtils';
03
04// Function to create a Magic instance for a specific network
05export const createMagicInstance = (network: Network) => {
06 const rpcUrl = getNetworkUrl(network);
07 const chainId = getChainId(network);
08
09 return new Magic('YOUR_MAGIC_API_KEY', {
10 network: {
11 rpcUrl,
12 chainId,
13 }
14 });
15};
16
17// Example: Create Magic instances for multiple networks
18const ethereumMagic = createMagicInstance(Network.ETHEREUM);
19const polygonMagic = createMagicInstance(Network.POLYGON);
20const zkSyncMagic = createMagicInstance(Network.ZKSYNC);
#Managing Network State in Your Application
To effectively switch between networks, implement a context or state management solution:
01import React, { createContext, useState, useContext, useEffect } from 'react';
02import { Magic } from 'magic-sdk';
03import { Network, getNetworkName } from './networkUtils';
04
05interface NetworkContextType {
06 currentNetwork: Network;
07 magicInstance: Magic | null;
08 switchNetwork: (network: Network) => void;
09 networkName: string;
10}
11
12const NetworkContext = createContext<NetworkContextType | undefined>(undefined);
13
14export const NetworkProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
15 // Default to Ethereum
16 const [currentNetwork, setCurrentNetwork] = useState<Network>(Network.ETHEREUM);
17 const [magicInstance, setMagicInstance] = useState<Magic | null>(null);
18
19 // Initialize or update Magic instance when network changes
20 useEffect(() => {
21 const newMagicInstance = createMagicInstance(currentNetwork);
22 setMagicInstance(newMagicInstance);
23
24 // Clean up previous instance when switching networks
25 return () => {
26 // Any cleanup needed for previous Magic instance
27 };
28 }, [currentNetwork]);
29
30 const switchNetwork = (network: Network) => {
31 setCurrentNetwork(network);
32 };
33
34 const networkName = getNetworkName(currentNetwork);
35
36 return (
37 <NetworkContext.Provider value={{
38 currentNetwork,
39 magicInstance,
40 switchNetwork,
41 networkName
42 }}>
43 {children}
44 </NetworkContext.Provider>
45 );
46};
47
48// Custom hook to use the network context
49export const useNetwork = () => {
50 const context = useContext(NetworkContext);
51 if (context === undefined) {
52 throw new Error('useNetwork must be used within a NetworkProvider');
53 }
54 return context;
55};
#Creating a Network Selector Component
Provide users with a clear UI to switch between networks:
01import React from 'react';
02import { useNetwork } from './NetworkContext';
03import { Network, getNetworkName, getNetworkToken } from './networkUtils';
04
05export const NetworkSelector: React.FC = () => {
06 const { currentNetwork, switchNetwork } = useNetwork();
07
08 const networks = [
09 Network.ETHEREUM,
10 Network.ETHEREUM_SEPOLIA,
11 Network.POLYGON,
12 Network.POLYGON_AMOY,
13 Network.ZKSYNC,
14 Network.ZKSYNC_SEPOLIA,
15 Network.ETHERLINK,
16 Network.ETHERLINK_TESTNET,
17 ];
18
19 return (
20 <div className="network-selector">
21 <label htmlFor="network-select">Current Network:</label>
22 <select
23 id="network-select"
24 value={currentNetwork}
25 onChange={(e) => switchNetwork(e.target.value as Network)}
26 >
27 {networks.map((network) => (
28 <option key={network} value={network}>
29 {getNetworkName(network)} ({getNetworkToken(network)})
30 </option>
31 ))}
32 </select>
33 </div>
34 );
35};
#Performing Network-Specific Operations
When interacting with the blockchain, use the active Magic instance:
01import { useNetwork } from './NetworkContext';
02import { ethers } from 'ethers';
03
04export const TokenTransfer: React.FC = () => {
05 const { magicInstance, networkName } = useNetwork();
06
07 const sendTransaction = async (recipient: string, amount: string) => {
08 if (!magicInstance) return;
09
10 try {
11 // Get provider from the active Magic instance
12 const provider = new ethers.providers.Web3Provider(magicInstance.rpcProvider);
13 const signer = provider.getSigner();
14
15 // Create and send transaction
16 const tx = await signer.sendTransaction({
17 to: recipient,
18 value: ethers.utils.parseEther(amount)
19 });
20
21 console.log(`Transaction sent on ${networkName}:`, tx.hash);
22 return tx;
23 } catch (error) {
24 console.error('Transaction failed:', error);
25 throw error;
26 }
27 };
28
29 // Component JSX...
30};
By implementing this architecture, your application can seamlessly support multiple blockchains while maintaining a cohesive user experience through Magic's consistent authentication layer.
#Supporting Non-EVM Chains
While this guide focuses on EVM-compatible chains, you can extend your multichain application to support non-EVM chains (like Solana or Bitcoin) using the same architectural pattern. The main differences will be in the chain-specific extensions, transaction signing, and sending logic.
#Example: Solana Integration
Creating a Solana Magic Instance
01import { Magic } from "magic-sdk";
02import { SolanaExtension } from "@magic-ext/solana";
03
04const createSolanaMagicInstance = (rpcUrl: string) => {
05 return new Magic("YOUR_API_KEY", {
06 extensions: [
07 new SolanaExtension({
08 rpcUrl,
09 }),
10 ],
11 });
12};
Sending Transactions
01import * as web3 from "@solana/web3.js";
02
03const sendSolanaTransaction = async (magic: Magic, amount: number) => {
04 const connection = new web3.Connection(web3.clusterApiUrl("devnet"));
Signing Messages
01const signSolanaMessage = async (magic: Magic, message: string) => {
02 const signedMessage = await magic.solana.signMessage(message);
03 return signedMessage;
04};
The core architecture of network selection, Magic instance management, and UI components remains the same across chains - only the blockchain-specific interactions need to be adapted for each chain type. As shown above, while EVM chains use ethers.js
for transaction handling, Solana uses the @solana/web3.js
library with its own transaction patterns and signing methods.
#Admin SDK: Managing User Wallets Server-Side
Your application may need to retrieve user wallets on the server side. The Magic Admin SDK provides several methods to fetch user metadata including their multichain wallets.
01import { Magic, WalletType } from "@magic-sdk/admin";
02
03const magic = new Magic("YOUR_ADMIN_SECRET_KEY");
The WalletType
enum supports multiple chains including:
- ETH (Ethereum and other EVM chains)
- SOLANA
- BITCOIN
- FLOW
- COSMOS
- And many others
This allows you to manage user wallet information across different blockchains from your backend services.