Deploy a smart contract to Validium using the validium-cli in under 5 minutes
This tutorial shows you how to deploy and interact with a smart contract on Validium in less than 5 minutes. It will help you get familiar with the Validium smart contract development and deployment process using different tools.
Have at least 0.5 Validium Devnet VLDM. If you need more, use one of the faucets.
validium-cli
Syntax:
npx validium-cli create <project-name>
Create Validium Project
Use the command below to create a project
npx validium-cli create my-project
Move to the just created project:
cd my-project
Create a .env file and add these environment variables:
WALLET_PRIVATE_KEY=
INFURA_API_KEY=
Add your account wallet private key here using which you want to deploy and interact with your contract. Also, you will need the Infura API key with the Holesky network enabled (the Underlying Ethereum network).
Review the Smart Contract
Review the contract inside contracts folder:
Counter.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;contract Counter {uint256public count =0;// EventseventGreet(string);eventCountUpdated(uint256);constructor() {// initializes the contract with a welcome messageemitGreet("Welcome to Validium Network!"); }functionincrement() public { count +=1;emitCountUpdated(count); }functiondecrement() public {require(count >0," UNDERFLOW: CANNOT DECREASE ANYMORE!"); count -=1;emitCountUpdated(count); }functiongetCount() publicviewreturns (uint256) {return count; }}
The Solidity smart contract contains three functions:
increment increases the value of the count state by 1 and emits the CountUpdated event.
decrement decreases the value of the count state by 1 and emits the CountUpdated event.
getCount returns the current value of the count state.
Validium is EVM compatible. You can write smart contracts with Solidity or Vyper and use existing popular libraries like OpenZeppelin, just like on Ethereum.
Compile and Deploy the Smart Contract
Use this command to compile the smart contract:
npx validium-cli compile
It will compile and generate an artifacts folder named artifacts-zk .
Logs:
$ npx validium-cli compile
Setting up compilation environment...
Compiling contracts for ZKsync Era with zksolc v1.5.7 and zkvm-solc v0.8.17-1.0.1
Compiling 1 Solidity file
Successfully compiled 1 Solidity file
Go to deploy.ts file inside deploy folder:
deploy.ts
import*as hre from"hardhat";import { Deployer } from"@matterlabs/hardhat-zksync";import { getWallet, verifyContract, verifyEnoughBalance } from"./utils";import { ethers } from"ethers";constdeployContract=async ( contractArtifactName:string, constructorArguments?:any[]) => {console.log(`\nStarting deployment process of "${contractArtifactName}"...`);constwallet=getWallet();constdeployer=newDeployer(hre, wallet);constartifact=await deployer.loadArtifact(contractArtifactName).catch((error) => {if (error?.message?.includes(`Artifact for contract "${contractArtifactName}" not found.` ) ) {console.error(error.message);throw`⛔️ Please make sure you have compiled your contracts or specified the correct contract name!`; } else {throw error; } });// Estimate contract deployment feeconstdeploymentFee=awaitdeployer.estimateDeployFee( artifact, constructorArguments || [] );console.log(`Estimated deployment cost: ${ethers.formatEther(deploymentFee)} ETH` );// Check if the wallet has enough balanceawaitverifyEnoughBalance(wallet, deploymentFee);// Deploy the contract to ZKsyncconstcontract=awaitdeployer.deploy(artifact, constructorArguments);constaddress=awaitcontract.getAddress();constconstructorArgs=contract.interface.encodeDeploy(constructorArguments);constfullContractSource=`${artifact.sourceName}:${artifact.contractName}`;// Display contract deployment infoconsole.log(`\n"${artifact.contractName}" was successfully deployed 🎉:`);console.log(` - Contract address: ${address}`);console.log(` - Contract source: ${fullContractSource}`);console.log(` - Encoded constructor arguments: ${constructorArgs}\n`);console.log(` - See on Validium Block Explorer: https://devnet.explorer.validium.network/address/${address}\n` );if (hre.network.config.verifyURL) {console.log(`Requesting contract verification...`);awaitverifyContract({ address, contract: fullContractSource, constructorArguments: constructorArgs, bytecode:artifact.bytecode, }); }return contract;};exportdefaultasyncfunction () {constcontractArtifactName="Counter";constconstructorArguments:any[] |undefined= [];awaitdeployContract(contractArtifactName, constructorArguments);}
Update the following:
contractArtifactName (check generated artifacts-zk folder, usually the same as the contract file name)
constructorArguments (if providing any arguments to the constructor)
Deploy to Validium Devnet:
syntax:
npx validium-cli deploy <script-file>
If <script-file> is not provided, then by default it will be deploy.ts from the deploy folder.
npx validium-cli deploy
This will deploy the compiled contract to the Validium Devnet.
Logs:
$ npx validium-cli deploy
Setting up deployment environment...
Starting deployment process of "Counter"...
Estimated deployment cost: 0.000019009462581388 ETH
"Counter" was successfully deployed 🎉:
- Contract address: 0xB168e33f0d03666590be03AdeAc92Bd76b3229af
- Contract source: contracts/Counter.sol:Counter
- Encoded constructor arguments: 0x
- See on Validium Block Explorer: https://devnet.explorer.validium.network/address/0xB168e33f0d03666590be03AdeAc92Bd76b3229af
Piece of cake right? 🎊
Check the Contract in Validium Block Explorer
Use the contract address from the deployment logs to see the deployed contract on the Validium Block Explorer or use the link from the logs directly.
You can also move to the Events tab and see the Greet event emitted through the constructor as a result of deployment:
Interact with the Deployed Contract
Go to interact.ts file inside deploy folder:
interact.ts
import*as hre from"hardhat";import { ethers } from"ethers";import { getWallet } from"./utils";// Address of the contract to interact withconstCONTRACT_ADDRESS="";constCONTRACT_NAME="Counter";if (!CONTRACT_NAME) throw"⛔️ Provide name of the contract to interact with!";if (!CONTRACT_ADDRESS)throw"⛔️ Provide address of the contract to interact with!";// An example of a script to interact with the contractexportdefaultasyncfunction () {console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`);// Load compiled contract infoconstcontractArtifact=awaithre.artifacts.readArtifact(CONTRACT_NAME);// Initialize contract instance for interactionconstcontract=newethers.Contract(CONTRACT_ADDRESS,contractArtifact.abi,getWallet() // Interact with the contract on behalf of this wallet );asyncfunctionincrement() {// Increment the counterconsole.log("Calling increment...");constincrementTx=awaitcontract.increment();console.log("Tx hash:",incrementTx.hash);awaitincrementTx.wait();console.log("Incremented!"); }asyncfunctiondecrement() {// Decrement the counterconsole.log("Calling decrement...");constdecrementTx=awaitcontract.decrement();console.log("Tx hash:",decrementTx.hash);awaitdecrementTx.wait();console.log("Decremented!"); }asyncfunctionfetchStoredInteger() {// Fetch the current stored integerconsole.log("Calling getCount...");constcurrentStoredInteger=awaitcontract.getCount();console.log(`Count: ${currentStoredInteger}`); }// listen to events-contract.on("CountUpdated", (newValue) => {console.log(`Event - CountUpdated: ${newValue}`); });awaitincrement();awaitincrement();awaitdecrement();awaitfetchStoredInteger();}
Update the following:
CONTRACT_ADDRESS (copy the contract address from the deployment logs and paste it here)
CONTRACT_NAME (contract artifact name from afticats-zk folder)
Run the interact script:
syntax:
npx validium-cli interact <script-file>
If <script-file> is not provided, then by default it will be interact.ts from the deploy folder.
npx validium-cli interact
The interact script above triggers three transactions (two increment and one decrement) and one fetch (getCount) to the deployed contract. It also listens to the CountUpdated event when emitted.