We've updated our Terms of Service. By continuing to use our services, you agree to the updated Terms.

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:

Typescript
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:

Typescript
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:

Typescript
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:

Typescript
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:

Typescript
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

Typescript
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

Typescript
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

Typescript
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.

Typescript
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.