Overview
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:
export enum Network {
POLYGON_AMOY = 'polygon-amoy',
POLYGON = 'polygon',
ETHEREUM_SEPOLIA = 'ethereum-sepolia',
ETHEREUM = 'ethereum',
ETHERLINK = 'etherlink',
ETHERLINK_TESTNET = 'etherlink-testnet',
ZKSYNC = 'zksync',
ZKSYNC_SEPOLIA = 'zksync-sepolia',
}
// Get RPC URL for the current network
export const getNetworkUrl = (network: Network): string => {
switch (network) {
case Network.POLYGON:
return 'https://polygon-rpc.com/';
case Network.POLYGON_AMOY:
return 'https://rpc-amoy.polygon.technology/';
case Network.ETHEREUM_SEPOLIA:
return 'https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY';
case Network.ETHEREUM:
return 'https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY';
case Network.ETHERLINK:
return 'https://node.mainnet.etherlink.com';
case Network.ETHERLINK_TESTNET:
return 'https://node.ghostnet.etherlink.com';
case Network.ZKSYNC:
return 'https://mainnet.era.zksync.io';
case Network.ZKSYNC_SEPOLIA:
return 'https://zksync-era-sepolia.blockpi.network/v1/rpc/public';
default:
throw new Error('Network not supported');
}
};
// Get chain ID for the current network
export const getChainId = (network: Network): number => {
switch (network) {
case Network.POLYGON:
return 137;
case Network.POLYGON_AMOY:
return 80002;
case Network.ETHEREUM_SEPOLIA:
return 11155111;
case Network.ETHEREUM:
return 1;
case Network.ETHERLINK:
return 42793;
case Network.ETHERLINK_TESTNET:
return 128123;
case Network.ZKSYNC:
return 324;
case Network.ZKSYNC_SEPOLIA:
return 300;
default:
throw new Error('Network not supported');
}
};
Creating Magic Instances for Multiple Networks
With the network utilities in place, you can create Magic instances for each supported network:
import { Magic } from 'magic-sdk';
import { Network, getNetworkUrl, getChainId } from './networkUtils';
// Function to create a Magic instance for a specific network
export const createMagicInstance = (network: Network) => {
const rpcUrl = getNetworkUrl(network);
const chainId = getChainId(network);
return new Magic('YOUR_MAGIC_API_KEY', {
network: {
rpcUrl,
chainId,
}
});
};
// Example: Create Magic instances for multiple networks
const ethereumMagic = createMagicInstance(Network.ETHEREUM);
const polygonMagic = createMagicInstance(Network.POLYGON);
const zkSyncMagic = createMagicInstance(Network.ZKSYNC);
Managing Network State in Your Application
To effectively switch between networks, implement a context or state management solution:
import React, { createContext, useState, useContext, useEffect } from 'react';
import { Magic } from 'magic-sdk';
import { Network, getNetworkName } from './networkUtils';
interface NetworkContextType {
currentNetwork: Network;
magicInstance: Magic | null;
switchNetwork: (network: Network) => void;
networkName: string;
}
const NetworkContext = createContext<NetworkContextType | undefined>(undefined);
export const NetworkProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
// Default to Ethereum
const [currentNetwork, setCurrentNetwork] = useState<Network>(Network.ETHEREUM);
const [magicInstance, setMagicInstance] = useState<Magic | null>(null);
// Initialize or update Magic instance when network changes
useEffect(() => {
const newMagicInstance = createMagicInstance(currentNetwork);
setMagicInstance(newMagicInstance);
// Clean up previous instance when switching networks
return () => {
// Any cleanup needed for previous Magic instance
};
}, [currentNetwork]);
const switchNetwork = (network: Network) => {
setCurrentNetwork(network);
};
const networkName = getNetworkName(currentNetwork);
return (
<NetworkContext.Provider value={{
currentNetwork,
magicInstance,
switchNetwork,
networkName
}}>
{children}
</NetworkContext.Provider>
);
};
// Custom hook to use the network context
export const useNetwork = () => {
const context = useContext(NetworkContext);
if (context === undefined) {
throw new Error('useNetwork must be used within a NetworkProvider');
}
return context;
};
Creating a Network Selector Component
Provide users with a clear UI to switch between networks:
import React from 'react';
import { useNetwork } from './NetworkContext';
import { Network, getNetworkName, getNetworkToken } from './networkUtils';
export const NetworkSelector: React.FC = () => {
const { currentNetwork, switchNetwork } = useNetwork();
const networks = [
Network.ETHEREUM,
Network.ETHEREUM_SEPOLIA,
Network.POLYGON,
Network.POLYGON_AMOY,
Network.ZKSYNC,
Network.ZKSYNC_SEPOLIA,
Network.ETHERLINK,
Network.ETHERLINK_TESTNET,
];
return (
<div className="network-selector">
<label htmlFor="network-select">Current Network:</label>
<select
id="network-select"
value={currentNetwork}
onChange={(e) => switchNetwork(e.target.value as Network)}
>
{networks.map((network) => (
<option key={network} value={network}>
{getNetworkName(network)} ({getNetworkToken(network)})
</option>
))}
</select>
</div>
);
};
When interacting with the blockchain, use the active Magic instance:
import { useNetwork } from './NetworkContext';
import { ethers } from 'ethers';
export const TokenTransfer: React.FC = () => {
const { magicInstance, networkName } = useNetwork();
const sendTransaction = async (recipient: string, amount: string) => {
if (!magicInstance) return;
try {
// Get provider from the active Magic instance
const provider = new ethers.providers.Web3Provider(magicInstance.rpcProvider);
const signer = provider.getSigner();
// Create and send transaction
const tx = await signer.sendTransaction({
to: recipient,
value: ethers.utils.parseEther(amount)
});
console.log(`Transaction sent on ${networkName}:`, tx.hash);
return tx;
} catch (error) {
console.error('Transaction failed:', error);
throw error;
}
};
// Component JSX...
};
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.
Solana Integration
Creating a Solana Magic Instance
import { Magic } from "magic-sdk";
import { SolanaExtension } from "@magic-ext/solana";
const createSolanaMagicInstance = (rpcUrl: string) => {
return new Magic("YOUR_API_KEY", {
extensions: [
new SolanaExtension({
rpcUrl,
}),
],
});
};
Sending Transactions
import * as web3 from "@solana/web3.js";
const sendSolanaTransaction = async (magic: Magic, amount: number) => {
const connection = new web3.Connection(web3.clusterApiUrl("devnet"));
// Transaction logic here
};
Signing Messages
const signSolanaMessage = async (magic: Magic, message: string) => {
const signedMessage = await magic.solana.signMessage(message);
return signedMessage;
};
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. 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.
import { Magic, WalletType } from "@magic-sdk/admin";
const 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.