How to use Magic with the Celo blockchain
#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
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
.
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
.
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
.
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.
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.
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
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:
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
.
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
.
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
.
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.
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.
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.