Sending CELO & Mento Stable Assets
How to connect to the Celo test network and tranfer tokens using ContractKit.
Hello Celo: sending value with Celo
In this guide we are going to write a Node.js script to introduce some of the basic concepts that are important to understand how Celo works. This will get us started with connecting to the Celo network and learning how to develop more advanced applications.
We assume you already have Node.js and NPM installed on your computer.
Learning Objectives
At the end of this guide, you will be able to:
- Connect to the Celo test network, called Alfajores
- Get test CELO, Mento cUSD (USD) and Mento cEUR (EUR) from the faucet
- Read account and contract information from the test network
- Transferring CELO, Mento cUSD and Mento cEUR on the test network
Getting Started
To start, clone this GitHub repo. This is a Node.js application.
git clone https://github.com/critesjosh/helloCelo.git
We will be using the Celo ContractKit SDK to interact with the Celo test network (Alfajores). Let's install it. It is already defined in the package.json, so we can get it with
cd helloCelo
npm install
Importing ContractKit
We will be writing our Node.js app in the helloCelo.js
file.
Import the contract kit into our script with
// 1. Import web3 and contractkit
const Web3 = require("web3");
const ContractKit = require("@celo/contractkit");
Now we can use the ContractKit to connect to the test network.
// 2. Init a new kit, connected to the alfajores testnet
const web3 = new Web3("https://alfajores-forno.celo-testnet.org");
const kit = ContractKit.newKitFromWeb3(web3);
At any point in the file you can console.log()
variables to print their output when you run the script.
Reading Alfajores
ContractKit contains a contracts
property that we can use to access certain information about deployed Celo contracts.
The Celo blockchain has two native assets, CELO (CELO) and the Mento cUSD. Both of these assets implement the ERC20 token standard from Ethereum. The CELO asset is managed by the CELO smart contract and Mento cUSD is managed by the cUSD contract. We can access the CELO contract via the SDK with kit.contracts.getGoldToken()
and the Mento cUSD contract with kit.contracts.getStableToken()
. These functions return promises, so we have to wait for them to resolve before we can interact with the token contracts. If you are unfamiliar with Promises in Javascript, check out this documentation. Promises are a common tool in blockchain development. In this guide, we use the async/await syntax for promises.
Let's read some token balances from the blockchain. Add the following line in the readAccount()
function.
// 3. Get the token contract wrappers
let celotoken = await kit.contracts.getGoldToken();
let cUSDtoken = await kit.contracts.getStableToken();
let cEURtoken = await kit.contracts.getStableToken("cEUR");
We can get the CELO balance of an account using the token wrappers like goldtoken.balanceOf(address)
. Let's check the balance of this address '0xD86518b29BB52a5DAC5991eACf09481CE4B0710d'
.
// 4. Address to look up
let anAddress = "0xD86518b29BB52a5DAC5991eACf09481CE4B0710d";
// 5. Get token balances
let celoBalance = await celotoken.balanceOf(anAddress);
let cUSDBalance = await cUSDtoken.balanceOf(anAddress);
let cEURBalance = await cEURtoken.balanceOf(anAddress);
// Print balances
console.log(`${anAddress} CELO balance: ${celoBalance.toString()}`);
console.log(`${anAddress} Mento cUSD balance: ${cUSDBalance.toString()}`);
console.log(`${anAddress} Mento cEUR balance: ${cEURBalance.toString()}`);
The balanceOf(address)
function also returns a Promise, so we wait for the promise to resolve then we print the result.
To view the balances, run the script from the termainal with
node helloCelo.js
Note that the balanceOf()
function returns objects with type BigNumber because balances are represented in Celo as a 256 bit unsigned integer, and JavaScript's number type cannot safely handle numbers of that size. Note also that the balance values are reported in units of CELO Wei, where one CELO = 10**18 CELO Wei.
Reading all account balances is a powerful feature of blockchains. Next, let's see how we can send value to each other on the testnet.
In order to do transfers (aka transactions), we need to:
- Create an account (by creating a private key)
- Fund it with test CELO and Mento cUSDs
- Sign and send transactions to the network
Accounts
We are accessing the Celo network via a remote node via HTTP requests at 'https://alfajores-forno.celo-testnet.org'
.
Don't worry about what this means right now, just understand that it is easier to get started using Celo by accessing remote nodes, rather than running them locally on your machine. You can read more about the details of the Celo network here.
Because we are accessing the network remotely, we need to generate an account to sign transactions and fund that account with test CELO.
There is a short script in getAccount.js
to either get a Celo account from a mnemonic in the .secret
file, or create a random account if the file is empty. In the script, we useweb3.js
to create a new private key/account pair. Web3.js is a popular javascript library for handling Ethereum related functionality. Celo is a cousin of Ethereum, so this library works well for generating Celo accounts.
We can now use this account
to get account information (ie the private key and account address) and to send transactions from account.address
. Add the following code to read the account balance. Continue adding to helloCelo.js
.
//
// Create an Account
//
// 6. Import the getAccount function
const getAccount = require("./getAccount").getAccount;
async function getBalances() {
// 7. Get your account
let account = await getAccount();
// 8. Get the token contract wrappers
let celotoken = await kit.contracts.getGoldToken();
let cUSDtoken = await kit.contracts.getStableToken();
let cEURtoken = await kit.contracts.getStableToken("cEUR");
// 9. Get your token balances
let celoBalance = await celotoken.balanceOf(account.address);
let cUSDBalance = await cUSDtoken.balanceOf(account.address);
let cEURBalance = await cEURtoken.balanceOf(account.address);
// Print your account info
console.log(`Your account address: ${account.address}`);
console.log(`Your account CELO balance: ${celoBalance.toString()}`);
console.log(`Your account Mento cUSD balance: ${cUSDBalance.toString()}`);
console.log(`Your account Mento cEUR balance: ${cEURBalance.toString()}`);
}
Run this script again with node helloCelo.js
. This will print 0
, as we have not funded the associated account yet.
Using the faucet
We can get free test CELO and Mento cUSDs on the test network for development via the Celo Alfajores faucet.
Copy your randomly generated account address from the console output mentioned above, and paste it into the faucet.
Once your account has been funded, run $ node helloCelo.js
again to see your updated balance.
Sending Value
We have an account with CELO and Mento cUSD in it, now how do we send tokens to another account? Remember the token wrappers we used to read account balances earlier? We can use the same wrappers to send tokens, you just need to add the private key associated with your account to ContractKit (see line 10).
The token wrappers have a method called transfer(address, amount)
that allows you to send value to the specified address (line 14).
You need to send()
the transaction to the network after you construct it. The send()
methods accepts an option that allows you to specify the feeCurrency
, which allows the sender to pay transaction fees in CELO or Mento cUSD. The default feeCurrency
is CELO. In the following example, let's pay transaction fees in CELO when we transfer CELO and pay with Mento cUSD when we transfer Mento cUSD.
The send()
method returns a transaction object. We will wait for the transaction receipt (which will be returned when the transaction has been included in the blockchain) and print it when we get it. This receipt contains information about the transaction.
After we read the receipt, we check the balance of our account again, using the balanceOf()
function. The logs print our updated balance!
You may notice that the account balance is a bit smaller than the amount of tokens that we sent. This is because you have to pay for every update to the network.
Add the following code to the send()
function in helloCelo.js
to send a transaction.
async function send() {
// 10. Get your account
let account = await getAccount();
// 11. Add your account to ContractKit to sign transactions
kit.connection.addAccount(account.privateKey);
// 12. Specify recipient Address
let anAddress = "0xD86518b29BB52a5DAC5991eACf09481CE4B0710d";
// 13. Specify an amount to send
let amount = 100000;
// 14. Get the token contract wrappers
let celotoken = await kit.contracts.getGoldToken();
let cUSDtoken = await kit.contracts.getStableToken();
let cEURtoken = await kit.contracts.getStableToken("cEUR");
// 15. Transfer CELO and cUSD from your account to anAddress
// Optional: specify the feeCurrency, default feeCurrency is CELO
let celotx = await celotoken
.transfer(anAddress, amount)
.send({ from: account.address });
let cUSDtx = await cUSDtoken
.transfer(anAddress, amount)
.send({ from: account.address, feeCurrency: cUSDtoken.address });
let cEURtx = await cEURtoken
.transfer(anAddress, amount)
.send({ from: account.address });
// 16. Wait for the transactions to be processed
let celoReceipt = await celotx.waitReceipt();
let cUSDReceipt = await cUSDtx.waitReceipt();
let cEURReceipt = await cEURtx.waitReceipt();
// 17. Print receipts
console.log("CELO Transaction receipt: %o", celoReceipt);
console.log("Mento cUSD Transaction receipt: %o", cUSDReceipt);
console.log("Mento cEUR Transaction receipt: %o", cEURReceipt);
// 18. Get your new balances
let celoBalance = await celotoken.balanceOf(account.address);
let cUSDBalance = await cUSDtoken.balanceOf(account.address);
let cEURBalance = await cEURtoken.balanceOf(account.address);
// 19. Print new balance
console.log(`Your new account CELO balance: ${celoBalance.toString()}`);
console.log(`Your new account Mento cUSD balance: ${cUSDBalance.toString()}`);
console.log(`Your new account Mento cUSD balance: ${cEURBalance.toString()}`);
}
Run $ node helloCelo.js
again to send the transactions and see the printed output in the console.
Connecting to a Ledger Device from a Web Application
The above instructions apply to building NodeJS applications. If you want to build an integration with a web application, you can still use the ContractKit by following slightly modified instructions.
The following code examples are typescript so should be stored in a .tsc
file, you will also need to install typescript and then compile your typescript to javascript with npx tsc
before you can run the code with node.
npm install --save-dev typescript
npm install web3 @celo/contractkit @celo/wallet-ledger @ledgerhq/hw-app-eth @ledgerhq/hw-transport-u2f @ledgerhq/hw-transport-webusb
Then, you can create a new instance of the ContractKit with the following code:
import { ContractKit, newKitFromWeb3 } from "@celo/contractkit";
import { newLedgerWalletWithSetup } from "@celo/wallet-ledger";
import Eth from "@ledgerhq/hw-app-eth";
import TransportU2F from "@ledgerhq/hw-transport-u2f";
import TransportUSB from "@ledgerhq/hw-transport-webusb";
import Web3 from "web3";
// Handle getting the Celo Ledger transport.
const getCeloLedgerTransport = () => {
if (window.USB) {
return TransportUSB.create();
} else if (window.u2f) {
return TransportU2F.create();
}
throw new Error(
"Ledger Transport not support, please use Chrome, Firefox, Brave, Opera or Edge."
);
};
// Handle creating a new Celo ContractKit
const getContractKit = async () => {
// Create a Web3 provider by passing in the testnet/mainnet URL
const web3 = new Web3("https://alfajores-forno.celo-testnet.org");
// Get the appropriate Ledger Transport
const transport = await getCeloLedgerTransport();
// Create a new instance of the ETH Ledger Wallet library
const eth = new Eth(transport);
// Use the Celo Ledger Wallet setup util
const wallet = await newLedgerWalletWithSetup(eth.transport);
// Instantiate the ContractKit
const kit: ContractKit = newKitFromWeb3(web3, wallet);
return kit;
};
Once you have successfully created the ContractKit, you can use the various Celo contracts to sign transactions with a connected Ledger device. For example, here's how to transfer gold tokens (just like above in the NodeJS example):
// Use the gold token contract to transfer tokens
const transfer = async (from, to, amount) => {
const celoTokenContract = await kit.contracts.getGoldToken();
const tx = await celoTokenContract.transfer(to, amount).send({ from });
const receipt = await tx.waitReceipt();
console.log("Transaction Receipt: ", receipt);
};
This is the basic setup to integrate the Celo Ledger App with a web application. You can also view the Celo Ledger App example codebase for some other examples of connecting to a Ledger Device from a web application.
Wrapping Up
Congratulations! You have accomplished a lot in this short introduction to developing on Celo.
We covered:
- Installing and setting up ContractKit
- Connecting to the Celo Alfajores network
- Getting the CELO contract wrapper
- Reading account balances using the CELO wrapper
- Generating a new account in Celo
- Funding an account using the Celo Alfajores Faucet
- Sending CELO