Building DApps for Celo (Part 1)
In this guide, we will try to get into smart contract development for the Celo platform. While there are already existing guides on the official wiki, they still lack quite a lot to provide a setup for production level development. We will try to fill those missing pieces in this post.
This guide assumes:
Experienced developer, already familiar with Typescript and npm tooling
No experience with writing Smart Contracts or programming in Solidity
Experience with @celo/contractkit is a plus, but not mandatory
If something in the guide isn’t making sense, you can check out the example repository that contains all the code described here:
Setup
Since Celo network is fully compatible with the Ethereum Virtual Machine (EVM), we can use existing tooling from the Ethereum ecosystem with only minor modifications. We will use:
Truffle: Suite of tools to compile, deploy and test smart contracts.
Ganache: Local blockchain server that can be used for full local testing of smart contracts.
Solidity: Programming language to write smart contracts. We will use Solidity 0.6.0.
Install `truffle` and Celo’s fork of `ganache-cli`:
> npm install -g truffle
> npm install -g @celo/ganache-cli
Truffle
To create a new truffle project in an empty folder, you can run:
> truffle init
> npm init
Truffle project has a fairly simple structure:
./contracts contains all the Solidity code.
./migrations contains Javascript code that deploys compiled contracts to the blockchain.
./test contains all the tests written in Javascript.
Typechain
By default, Truffle project is setup to be used with Javascript. However, we will change it to use Typescript instead. This will also allow us to generate typings for all our smart contracts and avoid many annoying issues during the development.
To use Truffle with Typescript we will use:
Typechain: Code generator to create Typescript types for smart contracts. Integrates really well with most IDEs.
If you have trouble getting Typechain setup properly, you can always check out their Truffle-Typechain example project here.
> npm install --save-dev @typechain/truffle-v5
> npm install --save-dev typechain
> npm install --save-dev truffle-typings
> npm install --save-dev typescript
Migrations and tests can now be written in typescript and will live in `./migrations-ts` and `./test-ts` folders. Compiled javascript code will still be placed in standard `./migrations` and `./test` folders.
> mv migrations migrations-ts
> mv test test-ts
Example `tsconfig.json` files to facilitate compiling code using typescript:
# tsconfig.json
{
"compilerOptions": {
"lib": ["ES2018", "DOM"],
"module": "CommonJS",
"moduleResolution": "node",
"strict": true,
"target": "ES2018",
"sourceMap": true,
"skipLibCheck": true,
"esModuleInterop": true
}
}
# tsconfig.migrate.json
{
"extends": "./tsconfig.json",
"include": ["./migrations-ts/*.ts", "./types/**/*.ts"],
"compilerOptions": {
"outDir": "./migrations"
}
}
# tsconfig.test.json
{
"extends": "./tsconfig.json",
"include": ["./test-ts/*.ts", "./types/**/*.ts"],
"compilerOptions": {
"outDir": "./test"
}
}
Now we can setup some scripts to compile our smart contracts, generate typings and to also compile all our Typescript code too:
# in package.json
"scripts": {
"compile": "truffle compile &&
typechain --target=truffle-v5 'build/contracts/*.json'",
"migrate": "npm run compile &&
npx tsc -p ./tsconfig.migrate.json &&
truffle migrate --reset",
"test": "npx tsc -p ./tsconfig.test.json && truffle test"
}
Compilation
> npm run compile
./build will contain all the compiled smart contracts.
./types will contain all the auto generated typings for the compiled smart contracts
Migrations
To run migrations, we will first need to start blockchain locally using `ganache-cli`, and setup `truffle-config.js` appropriately:
> ganache-cli --port 7545
# truffle-config.js
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*",
},
},
mocha: {},
compilers: {
solc: {
version: "0.6.0"
}
}
}
Lets also rename `1_initial_migration.js` to `1_initial_migration.ts` in `./migrations-ts` folder and make it a valid Typescript file:
# ./migrations-ts/1_initial_migration.ts
const Migrations = artifacts.require("Migrations");
module.exports = function (deployer) {
deployer.deploy(Migrations);
} as Truffle.Migration;
export {}
Now we can test the migrations:
> npm run migrate
Hello Contracts
At this point, we have spent quite a lot of effort to just set things up. Now its time to write some simple smart contract code and its tests.
# ./contracts/HelloContract.sol
//SPDX-License-Identifier: MIT
pragma solidity >= 0.6.0 < 0.8.0;
contract HelloContract {
uint256 public locked = 0;
function Lock(uint256 amount) external {
locked += amount;
}
function Unlock(uint256 amount) external {
locked -= amount;
}
}
We also need to add a migration to deploy our new contract.
# ./migrations-ts/2_deploy_hellocontract.ts
const HelloContract = artifacts.require("HelloContract");
module.exports = function (deployer) {
deployer.deploy(HelloContract);
} as Truffle.Migration;
export {}
Test compilation and migration of our new contract.
> npm run migrate
We can write all tests in Typescript. Check out more information in Truffle docs on how to write smart contract tests.
# ./test-ts/test-hellocontract.ts
const HelloContract = artifacts.require("HelloContract");
contract('HelloContract', (accounts) => {
it(`example test`, async () => {
const instance = await HelloContract.deployed()
let locked = await instance.locked()
assert.equal(locked.toNumber(), 0)
await instance.Lock(15)
locked = await instance.locked()
assert.equal(locked.toNumber(), 15)
await instance.Unlock(5)
locked = await instance.locked()
assert.equal(locked.toNumber(), 10)
})
})
We can now run tests:
> npm test
OR
> npm test ./test/test-hellocontract.js
Part 2…
In the next part, we will go over how to actually integrate with the Celo core smart contracts, both for development and for testing: https://wotrust.substack.com/p/building-dapps-for-celo-2