Skip to main content

=nil; 101

This tutorial acts as a 'primer' for working with =nil;. It explains how to perform essential actions with the cluster: creating a new smart account, deploying a contract, and calling a method inside this contract.

tip

This tutorial focuses on the =nil; CLI. To learn more about using Nil.js to create a smart account, deploy smart contracts, and send messages to these contracts, refer to the Cookbook.

Set up the =nil; CLI

The =nil; CLI is an easy-to-use tool for interacting with the cluster.

To install the CLI:

curl -fsSL https://github.com/NilFoundation/nil_cli/raw/master/install.sh | bash

To generate a new private key and set inside the CLI config:

nil keygen new

To point the CLI toward the RPC endpoint:

nil config set rpc_endpoint RPC_ENDPOINT

Create a new smart account

In =nil; a smart account is just a smart contract that can handle payments. There are no other structural differences between a smart account and a smart contract, which means that smart accounts can support any logic that can be expressed inside a smart contract.

Flow

To create a new smart account:

nil smart-account new

Expected response:

Contract SMART_ACCOUNT_ADDRESS balance is topped up by 100000000
Contract address: SMART_ACCOUNT_ADDRESS

To check the smart account bytecode:

nil contract code SMART_ACCOUNT_ADDRESS

To request a 'top up':

nil smart-account top-up 1000000

To see the smart account address and its public key:

nil smart-account info

Advanced tutorials

Deploy a smart contract

In =nil; contracts can be deployed on different execution shards that act as separate blockchains. Contracts can communicate with contracts on other shards, avoiding state fragmentation.

Any smart contract deployed in Ethereum-compatible networks and written in Solidity can be redeployed to =nil;

Flow

Create a new contract containing this code:

pragma solidity ^0.8.0;

contract Counter {
uint256 private value;

event ValueChanged(uint256 newValue);

receive() external payable {}

function increment() public {
value += 1;
emit ValueChanged(value);
}

function getValue() public view returns (uint256) {
return value;
}

function verifyExternal(
uint256 hash,
bytes memory authData
) external pure returns (bool) {
return true;
}
}

Access contract in the Playground

Compile the contract:

solc -o path/to/Counter --bin --abi path/to/Counter.sol --overwrite 

Deploy a contract with the =nil; CLI:

nil smart-account deploy path/to/Counter.bin --salt SALT

Expected output:

Contract address: COUNTER_ADDRESS
Transaction hash: TRANSACTION_HASH (shard 1)

Call the increment() function:

nil smart-account send-transaction COUNTER_ADDRESS increment --abi path/to/Counter.abi 

Retrieve the result:

nil contract call-readonly COUNTER_ADDRESS getValue --abi path/to/Counter.abi 

Advanced tutorials

Make a cross-shard call

When a smart contract makes a call to another smart contract deployed on a separate shard, the destination shard retrieves the resulting transaction and processes it. To make thing simpler, the Nil.sol library provides the async_call() function: it calls a special precompiled contract that allows for easily passing transactions between shards.

Flow

Create a new contract with the following code:

pragma solidity ^0.8.9;

import "@nilfoundation/smart-contracts/contracts/Nil.sol";

contract Caller {
using Nil for address;

receive() external payable {}

function call(address dst) public {
Nil.asyncCall(
dst,
msg.sender,
0,
abi.encodeWithSignature("increment()")
);
}

function verifyExternal(
uint256,
bytes calldata
) external pure returns (bool) {
return true;
}
}

Access contract in the Playground

Compile it and deploy it to Shard 2:

solc -o path/to/Caller --bin --abi path/to/Caller.sol --overwrite 
nil smart-account deploy path/to/Caller.bin --shard-id 2 --salt SALT

Expected output:

Contract address: CALLER_ADDRESS
Transaction hash: TRANSACTION_HASH (shard 1)

Note that CALLER_ADDRESS starts with 0x0002, which indicates that Caller is indeed deployed on Shard 2.

Send tokens to Caller and call the call() function:

nil smart-account send-tokens CALLER_ADDRESS 3000000 
nil smart-account send-transaction CALLER_ADDRESS call COUNTER_ADDRESS --abi path/to/Caller.abi 

Retrieve the result:

nil contract call-readonly COUNTER_ADDRESS getValue --abi path/to/Counter.abi 

Advanced tutorials

Nil.js

Refer to the Cookbook to learn how this flow would look like when using Nil.js.

Tokens

=nil; has base tokens that are used to pay for essential functionalities such as deploying contracts.

However, each contract can also create a custom token. A contract can only be the owner of one custom token. While custom tokens can be transferred between contracts, they cannot be used to pay for operations inside =nil;.

info

Non-owners cannot perform any operations with a custom token.

Flow

Create a new smart account:

nil smart-account new --salt SALT

Create a new token and withdraw it:

nil minter create-token NEW_SMART_ACCOUNT_ADDRESS 5000 new-token 

Check the tokens of the smart account:

nil contract tokens NEW_SMART_ACCOUNT_ADDRESS 

Expected output:

Contract tokens:
Balance: 50000 TokenId=Token_ID

Advanced tutorials