Skip to main content
ESC

Connect a Web Frontend

This tutorial walks through a minimal React frontend that connects MetaMask to Elastos Smart Chain (ESC) and uses ethers.js to read and write a smart contract. ESC mainnet uses Chain ID 20 (0x14); ESC testnet uses Chain ID 21 (0x15).

Prefer Vite for new projects

For new dApps, npm create vite@latest my-dapp -- --template react is faster and lighter than Create React App. CRA is still valid if your team standardizes on it; both work with the code below.

1. Create the React app

# Option A — Vite (recommended)
npm create vite@latest my-dapp -- --template react
cd my-dapp && npm install

# Option B — Create React App
npx create-react-app my-dapp
cd my-dapp && npm install

2. Install ethers.js

npm install ethers

This guide uses ethers v6 APIs (BrowserProvider, Contract, etc.).

3. Connect the wallet

Call eth_requestAccounts (wrapped by BrowserProvider) so MetaMask prompts the user. After approval, keep the returned address in React state. Before any contract call, confirm the active chain: 20 or 21 for ESC mainnet or testnet.

Injected window.ethereum

MetaMask injects window.ethereum. Always guard for undefined when the extension is missing, and prefer BrowserProvider over deprecated Web3Provider naming in ethers v6.

4. Read contract state

Use a Contract instance bound to the read-only provider, not the signer. View/pure calls do not need a user signature or gas. The provider talks to your RPC (https://api.elastos.io/esc on mainnet) and returns encoded results decoded by the ABI.

5. Write to the contract

Obtain a Signer with await provider.getSigner(), then bind the same ABI and address to that signer. State-changing methods return a transaction; await tx.wait() confirms inclusion. The user approves gas and the call in MetaMask.

6. Add ESC to MetaMask (EIP-3085)

Network Configuration

For the full list of network parameters (including EID), see Add Network to Wallet.

7. Switch chain if wrong network

Call wallet_switchEthereumChain with { chainId: "0x14" } or "0x15" first. If MetaMask returns error 4902 (unknown chain), call wallet_addEthereumChain with the same payload as in step 6, then switch again. The combined pattern is shown in the component’s ensureEsc helper below.

Complete example component

The following single file ties steps 3–7 together: connect, detect ESC, add/switch network, read with the provider, write with the signer.

For the canonical EIP-3085 field values, see Add Network to Wallet. The ESC_MAINNET and ESC_TESTNET objects below match that page so this tutorial stays one copy-paste file.

Create src/EscDapp.jsx (or replace App.jsx):

import { useCallback, useMemo, useState } from "react";
import { ethers } from "ethers";

const ESC_MAINNET = {
chainId: "0x14",
chainName: "Elastos Smart Chain",
nativeCurrency: { name: "ELA", symbol: "ELA", decimals: 18 },
rpcUrls: ["https://api.elastos.io/esc"],
blockExplorerUrls: ["https://esc.elastos.io/"],
};

const ESC_TESTNET = {
chainId: "0x15",
chainName: "Elastos Smart Chain Testnet",
nativeCurrency: { name: "tELA", symbol: "tELA", decimals: 18 },
rpcUrls: ["https://api-testnet.elastos.io/esc"],
blockExplorerUrls: ["https://esc-testnet.elastos.io/"],
};

// Replace with your deployed contract on ESC
const CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000000";
const CONTRACT_ABI = [
"function value() view returns (uint256)",
"function setValue(uint256 v)",
];

export default function EscDapp() {
const [account, setAccount] = useState(null);
const [chainOk, setChainOk] = useState(false);
const [readResult, setReadResult] = useState("");

const provider = useMemo(() => {
if (typeof window === "undefined" || !window.ethereum) return null;
return new ethers.BrowserProvider(window.ethereum);
}, []);

const ensureEsc = useCallback(async (testnet = false) => {
const target = testnet ? ESC_TESTNET : ESC_MAINNET;
try {
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: target.chainId }],
});
} catch (e) {
if (e?.code === 4902) {
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [target],
});
} else throw e;
}
const net = await provider.getNetwork();
const id = Number(net.chainId);
const want = testnet ? 21 : 20;
setChainOk(id === want);
}, [provider]);

const connect = async () => {
if (!provider) return alert("Install MetaMask");
const accs = await provider.send("eth_requestAccounts", []);
setAccount(accs[0]);
await ensureEsc(false);
};

const readContract = async () => {
if (!provider || !chainOk) return;
const readOnly = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
const v = await readOnly.value();
setReadResult(v.toString());
};

const writeContract = async () => {
if (!provider || !chainOk) return;
const signer = await provider.getSigner();
const c = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer);
const tx = await c.setValue(42n);
await tx.wait();
};

return (
<div style={{ fontFamily: "system-ui", padding: 24 }}>
<h1>ESC + ethers.js</h1>
<p>Account: {account ?? "—"}</p>
<p>On ESC (20/21): {chainOk ? "yes" : "no"}</p>
<button type="button" onClick={connect}>Connect & switch to ESC</button>
<button type="button" onClick={() => ensureEsc(true)}>Use testnet (21)</button>
<button type="button" onClick={readContract}>Read contract</button>
<button type="button" onClick={writeContract}>Write (MetaMask)</button>
<p>Read result: {readResult}</p>
</div>
);
}

Wire it from main.jsx / index.js by rendering <EscDapp />.

npm run dev
# or, for CRA:
npm start

Deploy the static build to any HTTPS host; MetaMask requires a secure context (localhost is exempt).

Chain IDs in hex

MetaMask expects chainId as a 0x-prefixed hex string: mainnet 200x14, testnet 210x15. Your app can compare Number((await provider.getNetwork()).chainId) to 20 or 21 for logic.

Summary

StepWhat you use
Connecteth_requestAccounts via BrowserProvider
Add networkwallet_addEthereumChain with ESC_MAINNET / ESC_TESTNET
Wrong chainwallet_switchEthereumChain, then add if error 4902
ReadContract + provider (no gas)
WritegetSigner() + signer-bound Contract (user pays gas)

Point CONTRACT_ADDRESS and CONTRACT_ABI at your real deployment on ESC before shipping.