Migration document from Contractkit
Hello devs 🌱 this is a migration path away from contractkit. This aims to give examples to help you move to viem.
Initialization​
- import Web3 from "web3";
- import { newKitFromWeb3 } from "@celo/contractkit";
-
- const web3 = new Web3("https://alfajores-forno.celo-testnet.org");
- const kit = newKitFromWeb3(web3);
+ import { createPublicClient, http } from 'viem'
+ import { celo, celoAlfajores } from 'viem/chains'
+
+ const publicClient = createPublicClient({
+ chain: celoAlfajores, // or celo for celo's mainnet
+ transport: http()
+ })
Basic usage​
While we cannot here show all the use-cases of contrackit or ethers or viem, let's try to give an overview of how they can be used for different goals.
Get address​
With viem:
- const accounts = await kit.web3.eth.getAccounts();
+ const accounts = await publicClient.getAddresses()
const defaultAccount = accounts[0];
Get wallet​
With viem:
+ import { privateKeyToAccount, createWalletClient, http } from 'viem/accounts'
+
+ const privateKey = "0x...";
+ const walletClient = createWalletClient({
+ transport: http(celoAlfajores.rpcUrls.default.http[0] as string),
+ chain: celoAlfajores,
+ });
+ const account = privateKeyToAccount(privateKey);
+
+ // Sign a message
+ await walletClient.signMessage({
+ account,
+ message: 'hello world',
+ });
+
+ // Sign a transaction
+ const transaction = {
+ to: '0x...',
+ value: 1000n,
+ gasLimit: 21000n,
+ gasPrice: 20000000000n,
+ };
+ const signedTransaction = await walletClient.signTransaction({
+ account,
+ transaction,
+ });
+
+ // Send the signed transaction
+ const txHash = await walletClient.sendSignedTransaction(signedTransaction);
Provider methods​
- const provider = kit.connection.web3.currentProvider
- kit.connection.getBlock(...)
- kit.connection.getTransaction(...)
+ const block = await publicClient.getBlock()
+ /**
+ * {
+ * baseFeePerGas: 10000n,
+ * number: 1234n,
+ * parentHash: "0x....",
+ * ...
+ * }
+ */
+ const tx = await publicClient.getTransaction({
+ hash: "0x...",
+ })
+ /**
+ * {
+ * blockHash: '0x...',
+ * blockNumber: 1234n,
+ * from: '0x...',
+ * ...
+ * }
+ */
Signer methods​
- const provider = kit.connection.web3.currentProvider
- const signer = provider.getSigner(kit.connection.defaultAccount)
+ const [account] = await walletClient.getAddresses()
+ const hash = await walletClient.sendTransaction({ account, to: "0x...", value: 1000n })
Contract interaction​
I'll show the most "basic" interaction, which is a transfer. On CELO, it comes with a twist, you can transfer 4 currencies, CELO, cUSD, cEUR, and cREAL.
You can get the addresses on these tokens by heading to the explorer and getting their abi and addresses, or you can also use our registry contract. You can also use the @celo/abis
package to get the ABIs directly.
import { getContract } from 'viem'
import { registryABI } from '@celo/abis/types/viem'
// this address is constant
const REGISTRY_CONTRACT_ADDRESS = '0x000000000000000000000000000000000000ce10'
const registryContract = getContract({
address: REGISTRY_CONTRACT_ADDRESS,
abi: registryABI,
publicClient,
})
async function CeloTokens(): Promise<[string, string][]> {
return Promise.all(
['GoldToken', 'StableToken', 'StableTokenEUR', 'StableTokenBRL'].map(async (token) => [
token,
await registryContract.read.getAddressForString(token),
])
)
}
Balance​
+ import { stableTokenABI } from '@celo/abis/types/viem'
- const contract = await kit.contracts.getGoldToken();
- const balance = await contract.balanceOf(wallet.address);
+ const tokenAddresses = await CeloTokens();
+ const cUSD = tokenAddresses["StableToken]
+ const balance = await client.readContract({
+ abi: tokenAbi,
+ address: cUSD,
+ functionName: "balanceOf",
+ args: [account.address],
+ });
Transfer​
Then, use the address of the token that you need and call the transfer method of the contract.
+ import { stableTokenABI } from '@celo/abis/types/viem'
- const CELO = await kit.contracts.getGoldToken();
- const txReceipt = await CELO.transfer('0x...', amount)
+ const tokenAddresses = await CeloTokens();
+ const cUSD = tokenAddresses["StableToken]
+ const { request } = await walletClient.simulateContract({
+ abi,
+ address: cUSD,
+ functionName: 'transfer',
+ args: [
+ '0x...', // to address
+ amount: 1000n,
+ ],
+ account: '0x...', // from address
+ })
+ const hash = await walletClient.sendTransaction(request);
Multicall​
While contractkit didn't directly support multicall, you could use libraries such as @dopex-io/web3-multicall
as such:
import Multicall from '@dopex-io/web3-multicall'
const MULTICALL_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11' // same on mainnet and alfajores
const multicall = new Multicall({
provider,
chainId,
multicallAddress: MULTICALL_ADDRESS,
})
const governance = await kit.contracts._web3Contracts.getGovernance()
const stageTxs = _dequeue.map((proposalId) => governance.methods.getProposalStage(proposalId))
const stages: string[] = await multicall.aggregate(stageTxs)
You now can use viem
directly to multicall since they have the address configured in the viem/chains
files.
const governanceAddress = await registryContract.read.getAddressForString(['Governance'])
const governanceContract = getContract({
address: governanceAddress,
abi: governanceABI,
publicClient,
})
const _dequeue = await governanceContract.read.getDequeue()
const stageCalls = _dequeue.map((proposalId) => ({
address: governanceAddress,
abi: governanceABI,
functionName: 'getProposalStage',
args: [proposalId],
}))
const stages = (await publicClient.multicall({ contracts: stageCalls })).map((x) => x.result)
Fee Currency​
With Viem's built in Celo transaction serializer and Celo block/transaction formatters it is easy to build a wallet that supports Celo's ability to pay gas fees with various erc20 tokens. Simply, import a Celo chain from viem/chain
and pass it to Viem's createWalletClient
. Once the client is created you can add the feeCurrency field to your transaction with the address of the token you want to use for gas.
import { celo } from 'viem/chains'
import { createWalletClient, privateKeyToAccount, type SendTransactionParameters, http } from 'viem'
const account = privateKeyToAccount(PRIVATE_KEY)
// ALFAJORES ADDRESS: Celo Mainnet can be fetched from the registry
const cUSDAddress = '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1'
const localAccountClient = createWalletClient({
account,
chain: celo,
})
const sendTransaction = (tx: SendTransactionParameters<typeof celo>) => {
return localAccountClient.sendTransaction(tx)
}
const hash = await sendTransaction({
feeCurrency: cUSDAddress,
value: BigInt(100000000),
to: '0x22579CA45eE22E2E16dDF72D955D6cf4c767B0eF',
})
Further reading​
For more in depth examples and documentation about viem specifically, I highly recommend checking out the extensive documentations of viem.
Another interesting application to help you migrate could be StCelo-v2.
You can checkout the changes going from react-celo
+ contractkit
to rainbowkit
+ wagmi
+ viem
in this pull-request.