Creating a wallet
This tutorial defines how wallets work in =nil; and outlines the different ways for creating a new wallet.
Definition
In =nil; a wallet is any smart contract that is able to pay for interactions with real-life users.
The traditional definition of a wallet in Ethereum mostly refers to externally-owned accounts (EOAs) that are secured via private keys and natively hold information about user's balances.
Wallets in =nil; differ from this definition. A =nil; wallet is always a smart contract, and it must be deployed as such. The wallet handles all the usual functionalities (e.g., authenticating requests) but it can also include complex logic such as multi-sig operations.
This means that in almost all cases an external message has to go through a wallet that signs the message and assigns a value to it. The message is only forwarded to its target smart contract for execution only after this process is complete.
It is possible to send funds to an address where a contract is supposed to be deployed. These funds will be stored at that address until the contract is finally deployed, at which point the contract will be able to access them.
Funds sent to an address without a deployed contract are safe, and another party cannot claim them by deploying another contract before the owners of the funds is able to deploy their own contract.
Pre-built and custom wallets
Because any smart contract can be a wallet, there is a distinction between pre-built and custom wallets.
A pre-built wallet is provided as part of =nil;. Such a wallet includes essential wallet functionalities as well as pre-defined constructor bytecode. The =nil; developer tools provide several ways of deploying pre-built wallets such as the nil wallet new
=nil; CLI command.
Presently, only one type of pre-built wallets is available. Additional types of pre-built wallets are WIP.
As pre-built wallets are provided as part of =nil; they are guaranteed to be valid and tested. An audit process for pre-built wallets is currently under consideration.
A custom wallet is any smart contract written by a user. This contract can support any flow permitted by Solidity code such as multi-sig authorization or token vesting. Custom wallets can be deployed just like any other smart contract.
New wallet creation
Via the =nil; CLI
The =nil; CLI offers a quick and convenient way to create a new wallet:
nil wallet new --salt SALT
This will produce the following response:
Contract NEW_WALLET_ADDRESS balance is topped up by 10000000
New wallet address: NEW_WALLET_ADDRESS
This wallet essentially acts as a typical EOA: it supports basic asset management and authentication operations. The wallet should also be initialized with an initial balance (which is also seen in the initial command response). Send the following request to check this:
nil wallet balance
The wallet can be 'topped up' by using the wallet top-up AMOUNT
command.
The wallet new
and the wallet top-up
commands only work in the =nil; devnet. The process for attaining tokens and 'topping up' an address will be different in the production network.
Via the client library
The Nil.js
client library exposes the WalletV1
class that allows for quickly creating and deploying a new wallet:
import {
Faucet,
HttpTransport,
LocalECDSAKeySigner,
PublicClient,
WalletV1,
generateRandomPrivateKey,
bytesToHex,
convertEthToWei,
} from "@nilfoundation/niljs";
const client = new PublicClient({
transport: new HttpTransport({
endpoint: RPC_ENDPOINT,
}),
shardId: 1,
});
const faucet = new Faucet(client);
const signer = new LocalECDSAKeySigner({
privateKey: generateRandomPrivateKey(),
});
const pubkey = await signer.getPublicKey();
const wallet = new WalletV1({
pubkey: pubkey,
salt: 100n,
shardId: 1,
client,
signer,
});
const walletAddress = wallet.address;
const faucetHash = await faucet.withdrawToWithRetry(
bytesToHex(walletAddress),
convertEthToWei(10),
);
await wallet.selfDeploy(true);
This example initializes a new wallet, provides it with a Signer
and requests a 'top up' to the wallet address. The wallet is then deployed.
When using the client library, seqno
defaults to 0
for every message sent via the wallet. If too many messages are sent at the same time with this seqno
, they will fail. To avoid this issue, set the seqno
manually whenever using the wallet:
wallet.sendMessage({
...
seqno: SEQNO,
...
})