Using Truffle with the Avalanche C-Chain

Introduction

Truffle Suite is a toolkit for launching decentralized applications (dapps) on the EVM. With Truffle you can write and compile smart contracts, build artifacts, run migrations and interact with deployed contracts. This tutorial illustrates how Truffle can be used with Avalanche's C-Chain, which is an instance of the EVM.

Requirements

You've completed Run an Avalanche Node and are familiar with Avalanche's architecture. You've also performed a cross-chain swap via the Transfer AVAX Between X-Chain and C-Chain tutorial to get funds to your C-Chain address.

Dependencies

  • Avash is a tool for running a local Avalanche network. It's similar to Truffle's Ganache.

  • NodeJS v8.9.4 or later.

  • Truffle, which you can install with npm install -g truffle

Start up a local Avalanche network

Avash allows you to spin up private test network deployments with up to 15 AvalancheGo nodes out-of-the-box. Avash supports automation of regular tasks via lua scripts. This enables rapid testing against a wide variety of configurations. The first time you use avash you'll need to install and build it.

Start a local five node Avalanche network:

cd /path/to/avash
# build Avash if you haven't done so
go build
# start Avash
./avash
# start a five node staking network
runscript scripts/five_node_staking.lua

A five node Avalanche network is running on your machine. When you want to exit Avash, run exit, but don't do that now, and don't close this terminal tab.

Create truffle directory and install dependencies

Open a new terminal tab to so we can create a truffle directory and install some further dependencies.

First, navigate to the directory within which you intend to create your truffle working directory:

cd /path/to/directory

Create and enter a new directory named truffle:

mkdir truffle; cd truffle

Use npm to install web3, which is a library through which we can talk to the EVM:

npm install web3 -s

We'll use web3 to set an HTTP Provider which is how web3 will speak to the EVM. Lastly, create a boilerplace truffle project:

truffle init

Development (local) network in Avash pre-funds some static addresses when created. We'll use @truffle/hdwallet-provider to use these pre-funded addresses as our accounts.

npm install @truffle/hdwallet-provider

Update truffle-config.js

One of the files created when you ran truffle init is truffle-config.js. Add the following to truffle-config.js.

const Web3 = require("web3");
const HDWalletProvider = require("@truffle/hdwallet-provider");
const protocol = "http";
const ip = "localhost";
const port = 9650;
const provider = new Web3.providers.HttpProvider(
`${protocol}://${ip}:${port}/ext/bc/C/rpc`
);
const privateKeys = [
"0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
"0x7b4198529994b0dc604278c99d153cfd069d594753d471171a1d102a10438e07",
"0x15614556be13730e9e8d6eacc1603143e7b96987429df8726384c2ec4502ef6e",
"0x31b571bf6894a248831ff937bb49f7754509fe93bbd2517c9c73c4144c0e97dc",
"0x6934bef917e01692b789da754a0eae31a8536eb465e7bff752ea291dad88c675",
"0xe700bdbdbc279b808b1ec45f8c2370e4616d3a02c336e68d85d4668e08f53cff",
"0xbbc2865b76ba28016bc2255c7504d000e046ae01934b04c694592a6276988630",
"0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60",
"0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c",
"0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a",
];
module.exports = {
networks: {
development: {
provider: () => {
return new HDWalletProvider({
privateKeys: privateKeys,
providerOrUrl: provider,
});
},
network_id: "*",
gas: 3000000,
gasPrice: 225000000000,
},
},
};

Note that you can change the protocol, ip and port if you want to direct API calls to a different AvalancheGo node. Also note that we're setting the gasPrice and gas to the appropriate values for the Avalanche C-Chain.

Add Storage.sol

In the contracts directory add a new file called Storage.sol and add the following block of code:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}

Storage is a solidity smart contract which lets us write a number to the blockchain via a store function and then read the number back from the blockchain via a retrieve function.

Add new migration

Create a new file in the migrations directory named 2_deploy_contracts.js, and add the following block of code. This handles deploying the Storage smart contract to the blockchain.

const Storage = artifacts.require("Storage");
module.exports = function (deployer) {
deployer.deploy(Storage);
};

Compile Contracts with Truffle

Any time you make a change to Storage.sol you need to run truffle compile.

truffle compile

You should see:

Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/Storage.sol
> Artifacts written to /path/to/build/contracts
> Compiled successfully using:
- solc: 0.5.16+commit.9c3226ce.Emscripten.clang

Accounts on C-chain

When deploying smart contracts to the C-Chain, truffle will default to the first available account provided by your C-Chain client as the from address used during migrations. We have added some pre-defined private keys as our accounts in the truffle-config.json. The first and default account should have some pre-funded AVAX.

Truffle Accounts

You can view imported accounts with truffle console.

To open the truffle console:

$ truffle console --network development

Note: If you see Error: Invalid JSON RPC response: "API call rejected because chain is not done bootstrapping", you need to wait until network is bootstrapped and ready to use. It should not take too long.

Inside truffle console:

truffle(development)> accounts
[
'0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC',
'0x9632a79656af553F58738B0FB750320158495942',
'0x55ee05dF718f1a5C1441e76190EB1a19eE2C9430',
'0x4Cf2eD3665F6bFA95cE6A11CFDb7A2EF5FC1C7E4',
'0x0B891dB1901D4875056896f28B6665083935C7A8',
'0x01F253bE2EBF0bd64649FA468bF7b95ca933BDe2',
'0x78A23300E04FB5d5D2820E23cc679738982e1fd5',
'0x3C7daE394BBf8e9EE1359ad14C1C47003bD06293',
'0x61e0B3CD93F36847Abbd5d40d6F00a8eC6f3cfFB',
'0x0Fa8EA536Be85F32724D57A37758761B86416123'
]

You can see balances with:

truffle(development)> await web3.eth.getBalance(accounts[0])
'50000000000000000000000000'
truffle(development)> await web3.eth.getBalance(accounts[1])
'0'

Notice that accounts[0] (default account) has some balance, while accounts[1] has no balance.

Scripting account funding

There is a convenient script that funds the accounts list . You can find it here. You can also download it using this command:

wget -nd -m https://raw.githubusercontent.com/ava-labs/avalanche-docs/master/scripts/fund-cchain-addresses.js;

You can run the script with:

truffle exec fund-cchain-addresses.js --network development

Script will fund 1000 AVAX to each account in accounts list above. After succesfully running the script you can check balances with:

truffle(development)> await web3.eth.getBalance(accounts[0]);
'50000001000000000000000000'
truffle(development)> await web3.eth.getBalance(accounts[1]);
'1000000000000000000'

Fund your account

If you wish to fund accounts your own, follow the steps in the Transfer AVAX Between X-Chain and C-Chain tutorial. You'll need to send at least 135422040 nAVAX to the account to cover the cost of contract deployments.

Personal APIs

Personal APIs interact with node’s accounts. web3 has some functions that uses it, e.g: web3.eth.personal.newAccount, web3.eth.personal.unlockAccount etc... However this API is disabled by default. It can be activated with C-chain/Coreth configs. Avash currently does not support activating this API. So if you want to use these features you need to run your own network manually with personal-api-enabled. See Create a Local Test Network/Manually and C-Chain Configs.

Run Migrations

Now everything is in place to run migrations and deploy the Storage contract:

truffle(development)> migrate --network development

You should see:

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Migrations dry-run (simulation)
===============================
> Network name: 'development-fork'
> Network id: 1
> Block gas limit: 99804786 (0x5f2e672)
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> block number: 4
> block timestamp: 1607734632
> account: 0x34Cb796d4D6A3e7F41c4465C65b9056Fe2D3B8fD
> balance: 1000.91683679
> gas used: 176943 (0x2b32f)
> gas price: 225 gwei
> value sent: 0 ETH
> total cost: 0.08316321 ETH
-------------------------------------
> Total cost: 0.08316321 ETH
2_deploy_contracts.js
=====================
Deploying 'Storage'
-------------------
> block number: 6
> block timestamp: 1607734633
> account: 0x34Cb796d4D6A3e7F41c4465C65b9056Fe2D3B8fD
> balance: 1000.8587791
> gas used: 96189 (0x177bd)
> gas price: 225 gwei
> value sent: 0 ETH
> total cost: 0.04520883 ETH
-------------------------------------
> Total cost: 0.04520883 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.13542204 ETH

If you didn't create an account on the C-Chain you'll see this error:

Error: Expected parameter 'from' not passed to function.

If you didn't fund the account, you'll see this error:

Error: *** Deployment Failed ***
"Migrations" could not deploy due to insufficient funds
* Account: 0x090172CD36e9f4906Af17B2C36D662E69f162282
* Balance: 0 wei
* Message: sender doesn't have enough funds to send tx. The upfront cost is: 1410000000000000000 and the sender's account only has: 0
* Try:
+ Using an adequately funded account

Interacting with your contract

Now the Storage contract has been deployed. Let's write a number to the blockchain and then read it back. Open the truffle console again:

Get an instance of the deployed Storage contract:

truffle(development)> let instance = await Storage.deployed()

This returns:

undefined

Writing a number to the blockchain

Now that you have an instance of the Storage contract, call it's store method and pass in a number to write to the blockchain.

truffle(development)> instance.store(1234)

You should see something like:

{
tx: '0x10afbc5e0b9fa0c1ef1d9ec3cdd673e7947bd8760b22b8cdfe08f27f3a93ef1e',
receipt: {
blockHash: '0x8bacbce7c9d835db524bb856288e3a73a6afbe49ab34abd8cd8826db0240eb21',
blockNumber: 9,
contractAddress: null,
cumulativeGasUsed: 26458,
from: '0x34cb796d4d6a3e7f41c4465c65b9056fe2d3b8fd',
gasUsed: 26458,
logs: [],
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
status: true,
to: '0x0d507b0467baef742f9cc0e671eddbdf6df41d33',
transactionHash: '0x10afbc5e0b9fa0c1ef1d9ec3cdd673e7947bd8760b22b8cdfe08f27f3a93ef1e',
transactionIndex: 0,
rawLogs: []
},
logs: []
}

Reading a number from the blockhain

To read the number from the blockchain, call the retrieve method of the Storage contract instance.

truffle(development)> let i = await instance.retrieve()

This should return:

undefined

The result of the call to retrieve is a BN (big number). Call its .toNumber method to see the value:

truffle(development)> i.toNumber()

You should see the number you stored.

1234

Summary

Now you have the tools you need to launch a local Avalanche network, create a truffle project, as well as create, compile, deploy and interact with Solidity contracts.