guides
Guide

How to use Magic with the Celo blockchain

Magic Staff · July 30, 2021

#Resources

#What is Celo

Celo is a mobile-first, EVM-compatible blockchain built for payment-focused decentralized applications. Having a mobile-first approach, apps built on Celo can reach a wider audience, with the nearly 4 billion smart phone users world-wide. And to fit the payment-focused narrative, Celo has a Celo Dollar (cUSD) native stablecoin so that transactions aren't exposed to price volatility.

Other reasons developers may consider Celo is that transactions are confirmed fast, usually in just a few seconds, with very low transaction fees. Using Celo with Magic, your users can authenticate through a web2-like login experience on your mobile or web app, and not have to worry about managing or securing their private keys themselves.

To connect to Celo with Magic, developers can simply pass in the Celo network URL when initiating a Magic instance. This guide will show how you can build a basic dapp on the Celo blockchain, allow users to call smart contracts and send transactions.

#Tutorial

This application will be broken out into two parts. Part 1 will be building a web app, and part 2 will be building a React Native mobile app.

#Part 1 - Web App

#Quick Start

Bash
01git clone https://github.com/magiclabs/example-celo-guide.git
02cd example-celo-guide
03mv .env.example .env // enter your API Key into .env (from https://dashboard.magic.link)
04yarn install
05yarn start

#Connecting to Celo

In magic.js, pass in the Celo network URL you want to connect to (Alfajores Testnet in this case) and initialize a new Web3 instance using Magic as the rpc provider.

Javascript
01import { Magic } from 'magic-sdk';
02import Web3 from 'web3';
03
04export const magic = new Magic(process.env.REACT_APP_MAGIC_PUBLISHABLE_KEY, {
05  network: {
06    rpcUrl: 'https://alfajores-forno.celo-testnet.org'
07  }
08});
09
10export const web3 = new Web3(magic.rpcProvider);

#Login with Magic

When users log in with Magic (through clicking on a link sent to their email), they will automatically be generated an Ethereum-compatible public / private key pair. Once logged in, a user can deposit funds to their newly created address, and as the developer, you can build out the wallet UI and logic with web3 libraries such as web3.js or ethers.js.

Javascript
01const login = useCallback(async () => {
02  await magic.auth.loginWithMagicLink({
03    email,
04    redirectURI: new URL('/callback', window.location.origin).href,
05  });
06  history.push('/');
07}, [email]);
08
09/**
10 * Saves the value of our email input into component state.
11 */
12const handleInputOnChange = useCallback(event => {
13  setEmail(event.target.value);
14}, []);
15
16return (
17  <div className='container'>
18    <h1>Please sign up or login</h1>
19    <input
20      type='email'
21      name='email'
22      required='required'
23      placeholder='Enter your email'
24      onChange={handleInputOnChange}
25      disabled={isLoggingIn}
26    />
27    <button onClick={login} disabled={isLoggingIn}>Send</button>
28  </div>
29);

#Viewing User Balance

Similar to how you would get a user's balance for an Ethereum application, since Celo is EVM compatible, you can call web3.eth.getBalance.

Javascript
01const fetchBalance = (address) => {
02  web3.eth.getBalance(address).then(bal => setBalance(web3.utils.fromWei(bal)))
03}
04
05return (
06<h1>Balance</h1>
07<div className="info">
08  {balance.toString().substring(0, 7)} CELO
09</div>
10)

#Send Transaction

Sending a transaction is also very simple. 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.

Javascript
01const sendTransaction = async () => {
02  if (!toAddress || !amount) return;
03  const { transactionHash } = await web3.eth.sendTransaction({
04    from: user.publicAddress,
05    to: toAddress,
06    value: web3.utils.toWei(amount)
07  });
08}
09
10return (
11 <div className="container">
12  <h1>Send Transaction</h1>
13  <input 
14    type="text" 
15    value={toAddress} 
16    onChange={(e) => setToAddress(e.target.value)} 
17    placeholder="To Address" 
18  />
19  <input 
20    type="text" 
21    value={amount} 
22    onChange={(e) => setAmount(e.target.value)} 
23    placeholder="Amount" 
24  />
25  <button onClick={sendTransaction}>Send Transaction</button>
26</div>
27)

#Calling Smart Contracts

The deployed HelloWorld smart contract has an update function which we'll call to update the message variable, which we're displaying in the web app.

Javascript
01const contractAddress = '0x1e1bF128A09fD30420CE9fc294C4266C032eF6E7';
02const contract = new web3.eth.Contract(abi, contractAddress);
03
04// Grabbing `message` variable value stored in the smart contract
05const fetchContractMessage = () => contract.methods.message().call().then(setMessage)
06
07// Update contract `message` value on the blockchain
08const updateContractMessage = async () => {
09  if (!newMessage) return;
10  const receipt = await contract.methods.update(newMessage).send({ from: user.publicAddress });
11}
12
13return (
14  <h1>Contract Message</h1>
15  <div className="info">{message}</div>
16
17  <h1>Update Message</h1>
18  <input 
19    type="text" 
20    value={newMessage} 
21    onChange={(e) => setNewMessage(e.target.value)} 
22    placeholder="New Message" />
23
24  <button onClick={updateContractMessage}>Update</button>
25)

And that's all that's involved for building a web app on Celo! A user can view their CELO token balance, send a transaction, and interact with smart contracts deployed to the Celo network.

#Part 2 - Mobile App (React Native)

Since Celo is a mobile-first blockchain, this will also be covering how you can build a react native app on this blockchain, with the same functionality as the web app example above.

#Quick Start

Bash
01git clone https://github.com/magiclabs/example-celo-guide-rn.git
02cd example-celo-guide-rn
03// enter your API key from https://dashboard.magic.link into the `Magic()` constructor in `magic.js`
04yarn install
05yarn start

#React Native App Setup

Run expo init (must have the expo-cli installed globally) and select the blank template to create our expo app. ⁠ Use this command to install the dependencies you'll need: yarn add @magic-sdk/react-native node-libs-browser react-native-webview web3. ⁠ ⁠Note: if you run into an error such as "Crypto" could not be found within the project, create a file called metro.config.js at the root of your project and add to it the following contents:

Javascript
01module.exports = {
02  resolver: {
03    extraNodeModules: require('node-libs-browser'),
04  },
05};

After these setup steps, you're good to start building!

#Connecting to Celo

In magic.js, pass in the Celo network URL you want to connect to (Alfajores Testnet in this case) and initialize a new Web3 instance using Magic as the rpc provider.

Javascript
01import { Magic } from '@magic-sdk/react-native';
02import Web3 from 'web3';
03
04export const magic = new Magic('YOUR_MAGIC_API_KEY', {
05  network: {
06    rpcUrl: 'https://alfajores-forno.celo-testnet.org'
07  }
08});
09
10export const web3 = new Web3(magic.rpcProvider);

#Logging in with Magic

When users log in with Magic (through clicking on a link sent to their email), they will automatically be generated a Celo public / private key pair. Once logged in, a user can deposit funds to their newly created address, and as the developer, you can build out the wallet UI and logic with web3 libraries such as web3.js or ethers.js.

Javascript
01export default function App() {
02  const [email, setEmail] = useState('');
03  const [user, setUser] = useState('');
04
05  // Trigger magic link for user to login / generate wallet
06  const login = async () => {
07    try {
08      await magic.auth.loginWithMagicLink({ email });
09      magic.user.getMetadata().then(setUser);
10    } catch(err) {
11      alert(err);
12    }
13  };
14
15  return (
16    <View style={styles.container}>
17      {
18      !user ? 
19        <View>
20          <Text style={styles.header}>Login or Signup</Text>
21          <TextInput
22            style={styles.input}
23            onChangeText={text => setEmail(text)}
24            value={email}
25            placeholder='Enter your email'
26          />
27          <View>
28            <Pressable style={styles.button} onPress={() => login()}><Text style={styles.buttonText}>Login</Text></Pressable>
29          </View>
30        </View> : 
31        <ScrollView>
32          // Show Logged In User View
33        </ScrollView>
34      }
35      {/* Below line is required to render the `Relayer` component into our app for Magic to properly work */}
36      <magic.Relayer />
37    </View>
38  );
39}

#View User Balance

Similar to how you would get a user's balance for an Ethereum application, since Celo is EVM compatible, you can call web3.eth.getBalance.

Javascript
01const [balance, setBalance] = useState('...');
02
03// Fetch logged in user's Celo balance
04const fetchBalance = (address) => {
05  web3.eth.getBalance(address).then(bal => setBalance(web3.utils.fromWei(bal)))
06}
07
08return (
09  <View style={styles.view}>
10    <Text style={styles.header}>Balance</Text>
11    <Text style={styles.info}>{balance} CELO</Text>
12  </View>
13)

#Send Transaction

Sending a transaction is also very simple. 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.

Javascript
01const [toAddress, setToAddress] = useState('');
02const [amount, setAmount] = useState('');
03const [sendTxnBtnText, setSendTxnBtnText] = useState('Send');
04const [sendTxnHash, setSendTxnHash] = useState('');
05
06// Submit a transaction to Celo network
07const sendTransaction = async () => {
08  if (!amount || !toAddress) return;
09  const { transactionHash } = await web3.eth.sendTransaction({
10    from: user.publicAddress,
11    to: toAddress,
12    value: web3.utils.toWei(amount)
13  });
14  setSendTxnHash(transactionHash);
15}
16
17return (
18  <View style={styles.view}>
19    <Text style={styles.header}>Send Transaction</Text>
20    <TextInput style={styles.input} value={toAddress} onChangeText={text => setToAddress(text)} placeholder="To..."></TextInput>
21    <TextInput style={styles.input} value={amount} onChangeText={text => setAmount(text)} placeholder="Amount..."></TextInput>
22    <Pressable style={styles.button} onPress={() => sendTransaction()}><Text style={styles.buttonText}>{sendTxnBtnText}</Text></Pressable>
23    <Text style={styles.text}>{sendTxnHash && <Text onPress={() => Linking.openURL(`https://alfajores-blockscout.celo-testnet.org/tx/${sendTxnHash}`)}>View Transaction ↗️</Text>}</Text>
24  </View>
25);

#Calling Smart Contracts

The deployed HelloWorld smart contract has an update function which we'll call to update the message variable, which we're displaying in the web app.

Javascript
01const contractAddress = '0x1e1bF128A09fD30420CE9fc294C4266C032eF6E7';
02const contract = new web3.eth.Contract(abi, contractAddress);
03const [message, setMessage] = useState('...');
04const [newMessage, setNewMessage] = useState('');
05const [updateContractBtnText, setUpdateContractBtnText] = useState('Update');
06const [updateContractTxnHash, setUpdateContractTxnHash] = useState('');
07
08const fetchContractMessage = () => contract.methods.message().call().then(setMessage);
09
10const updateContractMessage = async () => {
11  if (!newMessage) return;
12  let { transactionHash } = await contract.methods.update(newMessage).send({ from: user.publicAddress });
13  setUpdateContractTxnHash(transactionHash);
14}
15
16return (
17  <View style={styles.view}>
18    <Text style={styles.header}>Contract Message</Text>
19    <Text style={styles.info}>{message}</Text>
20    <Text style={styles.header}>Update Message</Text>
21    <TextInput style={styles.input} value={newMessage} onChangeText={text => setNewMessage(text)} placeholder="New Message"></TextInput>
22    <Pressable style={styles.button} onPress={() => updateContractMessage()}><Text style={styles.buttonText}>{updateContractBtnText}</Text></Pressable>
23    <Text style={styles.text}>{updateContractTxnHash && <Text onPress={() => Linking.openURL(`https://alfajores-blockscout.celo-testnet.org/tx/${updateContractTxnHash}`)}>View Transaction ↗️</Text>}</Text>
24  </View>
25);

#Done

You now have a web and mobile app built on Celo, which lets users login/create a wallet with just a magic link and interact with the Celo blockchain.

Let's make some magic!