How to Build a Decentralized App on Polygon and Ethereum with Magic

Magic Staff · April 19, 2021
How to Build a Decentralized App on Polygon and Ethereum with Magic


#Quick Start

01$ git clone
02$ cd magic-polygon
03$ mv .env.example .env // enter your Magic API key and Ropsten node URL (from Alchemy, Infura, etc)
04$ yarn install
05$ yarn start


With the rising gas prices on Ethereum, many developers are looking at other options that provide more scalability, faster transaction speed, and cheaper gas costs for users. Polygon, often referred to as an Ethereum side-chain, quickly established itself as one of the most popular solutions.

Polygon is a Proof-of-Stake layer 1 blockchain, and being EVM-compatible, established dapps on Ethereum can be easily re-deployed to Polygon with minimal-to-no code changes. Transactions on this side-chain, such as minting an NFT, can cost less than $0.01 compared to the same transaction on Ethereum which can cost several hundred dollars.

Read: Multichain: What It Is, Why It Matters

With Magic, developers can connect to Polygon by simply specifying the network URL when initiating a Magic instance. This guide will show how you can create a web3-enabled app, allow users to switch between Ethereum and Polygon networks, call smart contracts, and send transactions.

Note: `ETH` is the native token to Ethereum, `MATIC` is the native token to Polygon.


Note: this app was bootstrapped with the npx make-magic React template.

#Connecting to Ethereum / Polygon

In magic.js, we will need two Magic and two Web3 instances, one for each network, since we're allowing users to switch between the two. If you're only interested in connecting to Polygon, then only one instance of Magic and Web3 should be created. We also are adding = "ethereum" to be able to identify the Magic network we're creating.

You’ll use the same API key for both Magic instances so that the user’s public address does not change.

01import { Magic } from 'magic-sdk';
02import Web3 from 'web3';
05 * Configure Polygon Connection
06 */
07const polygonNodeOptions = {
08  rpcUrl: '',
09  chainId: 80001,
12export const magicMatic = new Magic(
14  { network: polygonNodeOptions }
15); = 'matic';
18export const maticWeb3 = new Web3(magicMatic.rpcProvider);
21 * Configure Ropsten Connection
22 */
23const ropstenNodeOptions = {
24  rpcUrl: process.env.REACT_APP_ROPSTEN_RPC,
25  chainId: 3,
28export const magicEthereum = new Magic(
30  { network: ropstenNodeOptions }
31); = 'ethereum';
34export const ethWeb3 = new Web3(magicEthereum.rpcProvider);

#Switching Between Networks

Users are able to switch between the Ethereum and Polygon networks with the select element dropdown list. Since one Magic instance points towards Ethereum, and the other Polygon, we simply update the instance that we’re using for our app based on whichever network the user selects.

01// pseudocode to just display the relevant code snippet
02import { magicEthereum, magicMatic, ethWeb3, maticWeb3 } from '../magic';
04const handleChangeNetwork = e => {
05 === 'ethereum' ? setMagic(magicEthereum) : setMagic(magicMatic);
06  fetchBalance(userMetadata.publicAddress);
07  fetchContractMessage();
10return (
11  <div className="info">
12    <select name="network" onChange={e => handleChangeNetwork(e)}>
13      <option value="ethereum">Ethereum (Ropsten Testnet)</option>
14      <option value="matic">Matic (Mumbai Testnet)</option>
15    </select>
16  </div>

#Viewing User Balance

A user's public address will be the same on both Ethereum and Polygon (as long as you are using the same API key for each instance) so a simple web3.eth.getBalance call is all that is needed for either network. Because the native token of Ethereum is ETH, and for Polygon is MATIC, we're displaying the appropriate token symbol based on the network we're connected to.

01// pseudocode to just display the relevant code snippet
02const fetchBalance = (address) => {
03  web3.eth.getBalance(address).then(bal => setBalance(web3.utils.fromWei(bal)))
06return (
08<div className="info">
09  {balance.toString().substring(0, 6)} { === 'matic' ? 'MATIC' : 'ETH'}

#Send Transaction

Sending a transaction is also very simple and the same for either network you're connected to. All that's needed is to provide an amount to send, and from and to addresses. If no gas or gasPrice are explicitly passed in, the gas limit and price will be calculated automatically. Otherwise, the values passed in will be used.

01// pseudocode to just display the relevant code snippet
02const web3 = === 'ethereum' ? ethWeb3 : maticWeb3;
04const sendTransaction = async () => {
05  if (!toAddress || !amount) return;
06  const receipt = await web3.eth.sendTransaction({
07    from: publicAddress,
08    to: toAddress,
09    value: web3.utils.toWei(amount),
10  });
13return (
14  <div className="container">
15    <h1>Send Transaction</h1>
16    <input type="text" value={toAddress} onChange={e => setToAddress(} placeholder="To Address" />
17    <input type="text" value={amount} onChange={e => setAmount(} placeholder="Amount" />
18    <button onClick={sendTransaction}>Send Transaction</button>
19  </div>

#Calling Smart Contracts

Separate smart contracts will need to be deployed on each Ethereum and Polygon for your users to interact with them. So you'll also need to dynamically know the correct address that the contract is deployed to in order to call it.

01// pseudocode to just display the relevant code snippet
02const network = === "ethereum" ? 'ethereum' : 'matic';
03const ropstenContractAddress = '0x8cb46E4bFc14Ce010dFbE5Ecb61BA64d798D3A67';
04const maticContractAddress = '0x9ebE0B009146643bb3560375A4562D8d89E135e9';
05const contract = new web3.eth.Contract(abi, network === "ethereum" ? ropstenContractAddress : maticContractAddress);
07// Grabbing `message` variable value stored in the smart contract
08const fetchContractMessage = () => contract.methods.message().call().then(setMessage)
10// Update contract `message` value on the blockchain
11const updateContractMessage = async () => {
12  if (!newMessage) return;
13  const receipt = await contract.methods.update(newMessage).send({ from: user.publicAddress });
16return (
17  <h1>Contract Message</h1>
18  <div className="info">{message}</div>
20  <h1>Update Message</h1>
21  <input
22    type="text"
23    value={newMessage}
24    onChange={(e) => setNewMessage(}
25    placeholder="New Message" />
27  <button onClick={updateContractMessage}>Update</button>


That's all there is to it! You've now got an app that allows users to create a wallet with just their email, and connect to multiple networks within your app. ⁠ ⁠More Tutorials: ⁠Building a low-code, opinionated approach to plug & play login

Passwordless Authentication with Magic and Gatsby.js

Let's make some magic!