⚠ make sure you already read through Hardhat 101 before moving on.️
💡 note on folder structure: we use monorepo to manages our smart contract and front-end code. Concretely, we use
lerna
which by default keeps all codebases under./packages/
folder.
- Prerequisites
- Hardhat Typescript sample project
- Add
OpenZeppelin
dependency - Add code formatting and linting
- Add test coverage
- Add gas reporter
- Add contract sizer
- Add commit lint and git hooks
- Add
TypeChain
plugin - Add deployment plugins
- Troubleshooting
Check the following installation:
node.js
andnpm
: preferably the latest LTS version ofnode
:[email protected]
.yarn
Learn the following languages:
Solidity
- The most recommended tutorial is CryptoZombies, at least Curriculum 1~4.
- Or read the official Solidity doc for more details.
TypeScript
- If you've never wrote any TypeScript before, we strongly recommend the "TypeScript Handbook".
- Regardless of whether you come from an OOP language like Java or C#, or a functional language like Haskell or ML, or a hybrid language like Go, Rust or Scala, you should take a look at their "Getting started section".
- Main reasons for our nudging to
TypeScript
overJavaScript
is its dominant popularity and superior protection against many common JS caveats via its type systems. (see StackOverflow's 2021 survey, or state of JS for yourself)
Create a new package and initialize with a basic sample Hardhat project (in JS).
mkdir -p packages/smart-contracts-boilerplate && cd packages/smart-contracts-boilerplate
npx hardhat # then choose the basic sample project
The following steps are almost verbatim from the official doc except we use yarn
as our package manager.
-
Remove
package-lock.json
and useyarn
instead. You should be able to see ayarn.lock
file being generated.⚠ you are only removing
package-lock.json
, NOTpackage.json
. Youryarn
still replies onpackage.json
to install all dependencies and other tasks.rm package-lock.json yarn
-
Install the following
npm
packages:yarn add --dev ts-node typescript chai @types/node @types/mocha @types/chai
-
Rename
hardhat.config.js
tohardhat.config.ts
mv hardhat.config.js hardhat.config.ts
and update the file, please refer to the doc here, if you don't understand some of the configurations specified below.
import { task, HardhatUserConfig } from "hardhat/config"; import "@nomiclabs/hardhat-waffle"; task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { const accounts = await hre.ethers.getSigners(); for (const account of accounts) { console.log(account.address); } }); const config: HardhatUserConfig = { defaultNetwork: "hardhat", paths: { sources: "./contracts", tests: "./test", artifacts: "./build/artifacts", cache: "./build/cache", }, solidity: { compilers: [ { version: "0.8.0", settings: { optimizer: { enabled: true, runs: 200, }, }, }, ], }, mocha: { timeout: 20000, }, }; export default config;
-
Add a
tsconfig.json
file, (learn more about what it does here){ "compilerOptions": { "target": "es2019", "module": "commonjs", "strict": true, "esModuleInterop": true, "resolveJsonModule": true, "outDir": "dist", "baseUrl": "." }, "include": ["./scripts", "./test"], "files": ["./hardhat.config.ts"] }
-
For convenience, introduce the following scripts in
package.json
:{ "scripts": { "compile": "yarn hardhat compile", "test": "yarn compile && yarn hardhat test" } }
Now, run
yarn compile
, you should be able to successfully compile your contracts. Woohoo! 🎉Notice there's
artifiacts/
andcache/
folders being created, both of which are git ignored, and if you dig intoartifiacts/contracts/Greeter.sol/Greeter.json
, you will see an ABI spec of the compiled contract. -
Finally, let's rename our test file:
mv test/sample-test.js test/greeter.spec.ts rm scripts/sample-script.js # we don't use this
Now run
yarn test
, you should see your test passing!Greeter Deploying a Greeter with greeting: Hello, world! Changing greeting from 'Hello, world!' to 'Hola, mundo!' ✓ Should return the new greeting once it's changed (415ms) 1 passing (416ms) ✨ Done in 1.84s.
Notice "Deploying a Greeter with greeting: Hello, world!" is actually being
console.log
from the contract constructor! This is super nice for debugging purposes, and thanks tohardhat/console.sol
for enabling this feature.
As demonstrated in CryptoZombies' tutorial or most other Solidity tutorials, OpenZeppelin contracts are a set of community vetted, widely used library.
-
Install these contracts in your dependencies
yarn add @openzeppelin/contracts
-
To verify that we can import the OpenZeppelin library, add the following line to your
contract/Greeter.sol
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
Then run
yarn compile
. If it compiled successfully, as it should, then it means you now haveOpenZeppelin
dependency installed.
We use solhint
for linting Solidity contract code, prettier
for formatting TypeScript/Javascript/Solidity code,
and sort-package-json
for sorting dependencies declaration in alphabetical order.
If you wondering "linter v.s. Formatter", please read here.
-
Add those tools to
devDependencies
yarn add --dev solhint prettier sort-package-json solhint-plugin-prettier prettier-plugin-solidity
-
Add Solidity linting rules in a new file
.solhint.json
, see explanations for these configs and a complete list here.{ "extends": "solhint:recommended", "plugins": ["prettier"], "rules": { "prettier/prettier": "error", "compiler-version": ["off"], "constructor-syntax": "warn", "quotes": ["error", "single"], "func-visibility": ["warn", { "ignoreConstructors": true }], "not-rely-on-time": "off", "private-vars-leading-underscore": ["warn", { "strict": false }] } }
-
Add format rules in a new file
.prettierrc
, see explanations for these configs and a complete list here. The *.sol rules adhere to the solididty style guide.{ "overrides": [ { "files": "**.sol", "options": { "printWidth": 99, "tabWidth": 2, "useTabs": false, "singleQuote": true, "bracketSpacing": false } }, { "files": ["**.ts", "**.js"], "options": { "printWidth": 145, "tabWidth": 2, "semi": true, "singleQuote": true, "useTabs": false, "endOfLine": "auto" } }, { "files": "**.json", "options": { "tabWidth": 2, "printWidth": 200 } } ] }
We further specify a list of files that we don't want format in a new file
.prettierignore
# General .prettierignore .solhintignore .husky .gitignore .gitattributes .env.example .env workspace.code-workspace .DS_STORE codechecks.yml # Hardhat coverage coverage.json build deployments dist # JS node_modules package-lock.json yarn.lock # Solidity contracts/mock
-
Add the following scripts to
package.json
{ "scripts": { "lint": "yarn solhint 'contracts/**/*.sol' && yarn prettier --check './**'", "lint:fix": "yarn sort-package-json && yarn prettier --write './**' && yarn solhint --fix 'contracts/**/*.sol'" } }
Now run
yarn lint
, you should see a bunch of error reported and justifications or hints to fix them:contracts/Greeter.sol 4:8 error Replace "hardhat/console.sol" with 'hardhat/console.sol' prettier/prettier 4:8 error Use single quotes for string literals quotes 5:8 error Replace "@openzeppelin/contracts/utils/math/SafeMath.sol" with '@openzeppelin/contracts/utils/math/SafeMath.sol' prettier/prettier 5:8 error Use single quotes for string literals quotes 8:1 error Delete ·· prettier/prettier 8:5 warning 'greeting' should start with _ private-vars-leading-underscore 10:1 error Delete ·· prettier/prettier 11:5 error Replace ····console.log("Deploying·a·Greeter·with·greeting:" with console.log('Deploying·a·Greeter·with·greeting:' prettier/prettier 11:21 error Use single quotes for string literals quotes 12:1 error Replace ········ with ···· prettier/prettier 13:3 error Delete ·· prettier/prettier 15:1 error Delete ·· prettier/prettier 16:5 error Delete ···· prettier/prettier 17:3 error Delete ·· prettier/prettier 19:1 error Replace ···· with ·· prettier/prettier 20:1 error Delete ···· prettier/prettier 20:21 error Use single quotes for string literals quotes 21:1 error Replace ········ with ···· prettier/prettier 22:1 error Delete ·· prettier/prettier ✖ 19 problems (18 errors, 1 warning)
A quick way to fix is running
yarn lint:fix
.After which you might still see some warning and errors that our linters can't make decisions on how to fix them. Go to
contract/Greeter.sol
, change line 8 tostring public greeting;
, change line 20 toconsole.log('Changing greeting from', greeting, 'to', _greeting);
.Now run
yarn lint
again, there should be no errors left.
We use solidity-coverage
plugin for test coverage report.
-
Install dependency
yarn add --dev solidity-coverage
-
Update
hardhat.config.ts
:import "solidity-coverage";
-
Add a new script command in
package.json
:{ "scripts": { "coverage": "hardhat coverage" } }
Then run
yarn coverage
, you should get:--------------|----------|----------|----------|----------|----------------| File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines | --------------|----------|----------|----------|----------|----------------| contracts/ | 100 | 100 | 100 | 100 | | Greeter.sol | 100 | 100 | 100 | 100 | | --------------|----------|----------|----------|----------|----------------| All files | 100 | 100 | 100 | 100 | | --------------|----------|----------|----------|----------|----------------| > Istanbul reports written to ./coverage/ and ./coverage.json > ✨ Done in 5.15s.
-
Finally add coverage related artifacts to your
.gitignore
file# Coverage coverage coverage.json
We use hardhat-gas-reporter
plugin for gas consumption report.
Normally we don't want to see gas reports on every test run, therefore we only enable it with an environment variable REPORT_GAS
.
To set temporary env variable only in the context of a command, we use cross-env
.
In step 5 below, we further use dotenv
to get secret/local variables from .env
file.
-
Install dependencies
yarn add --dev hardhat-gas-reporter cross-env dotenv
-
Add configuration to your
hardhat.config.ts
, see the list of configuration options.import "hardhat-gas-reporter"; const config: HardhatUserConfig = { // ... gasReporter: { currency: "USD", enabled: process.env.REPORT_GAS ? true : false, showMethodSig: true, onlyCalledMethods: false, }, };
-
Add a new script command to
package.json
{ "scripts": { "gas": "cross-env REPORT_GAS=1 yarn hardhat test" } }
-
Run
yarn gas
to get a gas report from our tests run. -
Optionally, if you want to get an actual market price, we integrate with CoinMarketCap's API (Free tier is more than enough for personal use).
Copy the [
.env.example
] file and paste it into your own.env
file, fill in your CoinMarketCap API key:COINMARKETCAP_API_KEY=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx
Jump back to
hardhat.config.ts
, add the following extra configuration:import * as dotenv from "dotenv"; dotenv.config(); const config: HardhatUserConfig = { // ... gasReporter: { // ... coinmarketcap: process.env.COINMARKETCAP_API_KEY, }, };
Run
yarn gas
again, and you shall see the USD cost.·------------------------------------|---------------------------|-------------|-----------------------------· | Solc version: 0.8.0 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │ ·····································|···························|·············|······························ | Methods · 74 gwei/gas · 2868.09 usd/eth │ ·············|·······················|·············|·············|·············|···············|·············· | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ ·············|·······················|·············|·············|·············|···············|·············· | Greeter · greet() · - · - · - · 0 · - │ ·············|·······················|·············|·············|·············|···············|·············· | Greeter · greeting() · - · - · - · 0 · - │ ·············|·······················|·············|·············|·············|···············|·············· | Greeter · setGreeting(string) · - · - · 34658 · 2 · 7.36 │ ·············|·······················|·············|·············|·············|···············|·············· | Deployments · · % of limit · │ ·····································|·············|·············|·············|···············|·············· | Greeter · - · - · 422738 · 1.4 % · 89.72 │ ·------------------------------------|-------------|-------------|-------------|---------------|-------------·
Be aware of a bug from upstream in
hardhat-gas-report
, if you don't see the price reporting, it's most likely due to this bug.
-
Install dependencies
yarn add --dev hardhat-contract-sizer
-
Add configuration to your
hardhat.config.ts
, see the list of configuration options.import "hardhat-contract-sizer"; const config: HardhatUserConfig = { // ... contractSizer: { alphaSort: true, disambiguatePaths: false, runOnCompile: true, strict: true, }, };
-
Add a new script command to
package.json
{ "scripts": { "size": "yarn hardhat size-contracts" } }
-
Run
yarn size
to get the contract size report for our compiled contract. Alternatively, you should also see thereport when you doyarn compile
.
To enforce standardized, conventional commit messages, we use commitlint
.
We recommend skimming through "benefits of commitlint" first if you haven't used it before.
Apparently, you should read the conventional rules that your future commit messages should adhere to.
To lint our commit message upon each git commit
, we need to create git hooks, for that we use husky
.
For avid explorers, you can read this blog post to understand some design decisions of husky
.
⚠ If you're running from the cloned hello-dapp
repo, then you can skip this section as commitlint
and husky
hooks had been configured already.
Else if you are building a contract package in your own monorepo, then you should add husky
in root package.json
instead.
Choose one of the two depending on your project setup:
-
Install dependencies
# Install commitlint cli and conventional config yarn add --dev @commitlint/config-conventional @commitlint/cli husky
-
Configure
commitlint
to use conventional configecho "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
Activate/initialize
husky
(one-time):npx husky-init && yarn
And you should be able to see a
.husky/pre-commit
file created for you. -
First modify
.husky/pre-commit
file to run linting instead:#!/bin/sh . "$(dirname "$0")/_/husky.sh" yarn lint:fix
Then add a new hook to ensure proper commit message:
# ensure proper commit messages npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
⚠ Again, if you're running from the cloned hello-dapp
repo, then you can skip this section as commitlint
and husky
hooks had been configured already.
-
Go back to Monorepo project root, then run
yarn add --dev @commitlint/config-conventional @commitlint/cli husky echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js # enable Git hooks yarn husky install # ensure correct format and linting before commit yarn husky add .husky/pre-commit 'yarn lint:fix' git add .husky/pre-commit # ensure proper commit messages yarn husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"' git add .husky/commit-msg
Add the following lines to
package.json
in monorepo's root:{ "scripts": { "lint": "yarn lerna run lint", "lint:fix": "yarn lerna run lint:fix" } }
Done. Go ahead and try commit with some bad error message such as
foo blah did something
, the hooks should prevent you from committing. 🛡️
Next stop, we add a super helpful tool called TypeChain
to our project template.
The one-liner pitch is "automatically generate TypeScript bindings for smart contracts".
What that means is, when writing tests or later on writing deployment scripts, we are likely to invoke functions or query getters defined in our contracts.
Right now it doesn't seem a hustle to remember each function signature (i.e. the function name and input/output parameters and their types) since our toy Greeter.sol
is simple;
but what if our contracts get complicated with dozens of functions?
Wouldn't it be handy the auto-completion engine in your editor/IDE knows these function signatures and offer you rich type information as you code? -- that's what TypeChain
provides!
While TypeChain
works with many other frameworks, we will be using its Hardhat
plugin 👷.
-
Install dependencies, (we use
waffle+ethers.js
stack)yarn add --dev typechain @typechain/hardhat @typechain/ethers-v5
-
Ensure you have the following imports in
hardhat.config.ts
:import "@typechain/hardhat"; import "@nomiclabs/hardhat-ethers"; import "@nomiclabs/hardhat-waffle";
Add
TypeChain
plugin related configuration, more docs here:const config: HardhatUserConfig = { // ... typechain: { outDir: "typechained", target: "ethers-v5", }, };
Run
yarn compile
, upon completion, you should be able to see a newly generatedtypechained/
folder which contains TypeScript types info of your smart contracts.💡 a bit explanation here: by default, all the types artifacts generated would be outputted to
typechain/
. The primarily reason we customize it tooutDir: typechained
is to avoid confusion between importing from@typechaind
v.s. importing from an npm package named@typechain
. -
To enable internal types import similarly to a published package, we update our
tsconfig.json
:{ "compilerOptions": { "paths": { "@typechained": ["typechained/index"] } } }
-
Finally, jump to your
test/greeter.spec.ts
and change to the following (pay attention toNOTICE HERE
comments):import { expect } from "chai"; import { ethers } from "hardhat"; // NOTICE HERE: import autogenerated types import { Greeter, Greeter__factory } from "@typechained"; describe("Greeter", function () { // NOTICE HERE: we can give our contract a `Greeter` type!! instead of an `any` type. let greeter: Greeter; let greeterFactory: Greeter__factory; beforeEach(async () => { greeterFactory = (await ethers.getContractFactory( "Greeter" )) as Greeter__factory; greeter = await greeterFactory.deploy("Hello, world!"); }); it("Should return the new greeting once it's changed", async function () { // NOTICE HERE: try type this yourself, your IDE's auto-completion should suggest available // functions of `greeter` as you type. await greeter.deployed(); expect(await greeter.greet()).to.equal("Hello, world!"); const setGreetingTx = await greeter.setGreeting("Hola, mundo!"); // wait until the transaction is mined await setGreetingTx.wait(); expect(await greeter.greet()).to.equal("Hola, mundo!"); }); });
Note: without the customized path declaration in the last step, you can still import types via relative path
import { Greeter } from '../typechaind/index';
-- but it is (arguably) not as nice as our import style which treats@typechaind
as just another package.
Now with smart contracts written and locally tested, it's time for live testnet/mainnet deployments, for which we use hardhat-deploy
plugin.
Before deploying, we would like to remove debug-related harness such as console.sol
-- for which we use hardhat-preprocessor
.
-
Install the plugin:
yarn add --dev hardhat-preprocessor
Add the following to your
hardhat.config.ts
:import { removeConsoleLog } from "hardhat-preprocessor"; const config: HardhatUserConfig = { // ... preprocess: { eachLine: removeConsoleLog( (hre) => hre.network.name !== "hardhat" && hre.network.name !== "localhost" ), }, };
The configuration above, as explained here, remove any
console.log()
in your contract when connecting to live networks.Now back to deployments.
💡 tips: most steps below offer only minimum clarifications, you are strongly recommended to read the
README
of thehardhat-deploy
repo for more under-the-hood explanations and other available setup options for more complicated deployment flows. Also when lost, try to find template/example deployment setups here.
-
Install dependencies
yarn add --dev hardhat-deploy
-
Update
hardhat.config.ts
import "hardhat-deploy"; const config: HardhatUserConfig = { // ... networks: { hardhat: { chainId: 1337, // temporary for MetaMask support: https://github.com/MetaMask/metamask-extension/issues/10290 }, }, paths: { // ... deploy: "./scripts/deploy", }, namedAccounts: { deployer: { default: 0, // by default, take the first account as deployer rinkeby: "0x5238A644636946963ffeDAc52Ec53fb489D3a1CD", // on rinkeby, use a specific account }, }, };
- To use
scripts/deploy/
as the folder for all deployment scripts (personal preference, you can also go with default path which isdeploy/
, see doc here), - The default
chainId
ofhardhat
network andlocalhost
network are31337
, but we can change it to something else (as long as not colliding with others). Specifically, due to a MetaMask compatibility issue, we change it1337
. - The differences between
hardhat
andlocalhost
is roughly: the latter is a separate, long-running process where changes made to it can be persistent locally across scripts and invocations; whereas the former is usually an ephemeral session (e.g. when you run fresh tests) that would shut down once scripts are done running (changes to the network are not persistent). - We "named" the first account as the
deployer
, (to get the full list of accounts hardhat generated for you, runyarn hardhat accounts
task). When pushing to testnet or main net, we usually use one of our accounts in our MetaMask wallet with safer, protected private key.
- To use
-
Create a new deployment script
./scripts/deploy/001-Greeter.deploy.ts
:import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; export const INITIAL_GREET: { [chainId: string]: string } = { "1337": "Bonjour localhost!", "4": "Guten tag, Rinkeby!", }; const deployFunc: DeployFunction = async ( hre: HardhatRuntimeEnvironment ) => { const { deployer } = await hre.getNamedAccounts(); const chainId = await hre.getChainId(); await hre.deployments.deploy("Greeter", { from: deployer, args: [INITIAL_GREET[chainId]], log: true, }); }; deployFunc.tags = ["Greeter"]; export default deployFunc;
Note on the filename: as documented here, when you later run
yarn deploy --network <NETWORK>
, hardhat will "scan for files in alphabetical order and execute them in turn". If your deployment workflow is straightforwardly in a sequential order, then naming your deployment scripts usingxxx-<contract/purpose>.deploy.ts
wherexxx
dictates the order, would be sufficient. However, if your deployment workflow is more complicated with library linking or inter-dependencies or even conditional deployments, then you should check outDeployFunction
'sskip
,dependencies
, andrunAtTheEnd
fields here as well as library linking config.Then add this to your
package.json
for slightly shorthanded commands:{ "scripts": { "deploy": "yarn hardhat deploy" } }
-
Now, first start a local node in a new terminal window:
yarn hardhat node
, you should seeStarted HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
.Jump to another terminal session, run
yarn deploy --network localhost
, you should see ourGreeter.sol
successfully deployed.web3_clientVersion Contract deployment: Greeter Contract address: 0x5fbdb2315678afecb367f032d93f642f64180aa3 Transaction: 0xf87999b7500e0ded42ddd10982e458c7fbcc054fd82fcaad893f45c2ecc037b8 From: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 Value: 0 ETH Gas used: 542532 of 542532 Block #1: 0x13b50db64e9e7120d870078b9b41f802c908161145cf1cf92e9ce32e35d80fd2 eth_chainId eth_accounts (2) eth_chainId (2) eth_getTransactionByHash eth_blockNumber eth_chainId
- Just for experimentation, you can try to run the same deployment script again, you will see
hardhat
by default won't create a duplicated contractreusing "Greeter" at 0x5FbDB2315678afecb367f032d93F642f64180aa3
-- this is a clear indication that our changes made to our local blockchain are persistent. - You should a new folder
deployemnts/
generated, go ahead and explore what's inside.
- Just for experimentation, you can try to run the same deployment script again, you will see
-
Finally, let's try to test against our deployed contract. Copy
test-local-deployed-greeter.ts
to yourscripts/test-local-deployed-greeter.ts
.Run
yarn hardhat run ./scripts/test-local-deployed-greeter.ts --network localhost
You should see our
assert((await greeter.greet()) === 'Bonjour localhost!')
passed! If you want, you can also tryconsole.log(greeterDeployment.address)
which would print the same address shown in the last step.
Now you are ready for deploying to live [test|main] networks under your MetaMask accounts.
-
We will need to use our Alchemy hosted endpoints for live testnet access, and use wallet mnemonic (the master secret seed) to derive and sign our deployment transactions. If you haven't set up Alchemy, please refer to warm up.
First, create a new file at your
utils/network.ts
and copy this file over.Update your
hardhat.config.ts
to use alchemy endpoints forrinkeby
testnet, and use your own mnemonic (i.e. your MetaMask wallet) to send transactions:import { nodeUrl, accounts } from "./utils/network"; const config: HardhatUserConfig = { networks: { hardhat: { chainId: 1337, // temporary for MetaMask support: https://github.com/MetaMask/metamask-extension/issues/10290 }, localhost: { url: nodeUrl("localhost"), }, rinkeby: { url: nodeUrl("rinkeby"), accounts: accounts("rinkeby"), }, // can configure other networks, see examples: // https://github.com/wighawag/template-ethereum-contracts/blob/main/hardhat.config.ts }, };
-
Run
cp .env.example .env
Edit your
.env
file by filling your API tokens and wallet mnemonic.On wallet mnemonic: we will be using a mnemonic wallet whose accounts are all deterministically derived from a master secret phrase (usually 12 words). You can
- either directly use your MetaMask's wallet by revealing its secret seed and paste it into
.env
- or run
yarn hardhat run ./scripts/generate-new-mnemonic.ts
and then go to your MetaMask and send some Ethers to the newly created wallet address (since deploying contracts require gas to pay transaction fee).
Also do remember to update the
deployer
's address configuration inhardhat.config.ts
to the address associated with your mnemonic wallet.const config: HardhatUserConfig = { namedAccounts: { deployer: { default: 0, // by default, take the first account as deployer rinkeby: "0x5238A644636946963ffeDAc52Ec53fb489D3a1CD", // !!! CHANGE THIS TO YOUR ADDRESS !!! }, }, };
- either directly use your MetaMask's wallet by revealing its secret seed and paste it into
-
Run
yarn deploy --network rinkeby
You should see (be patient, this might take some time):
deploying "Greeter" (tx: 0x41ef031e20b772de6b43a820e1ffdfc80b3d7fbdb978a75d13dee7a1b554f237)...: deployed at 0xD4deB045fb89E750864a7349087A6674C1E79F78 with 542664 gas ✨ Done in 71.73s.
You should be able to see your transactions and deployed contracts on Etherscan. For example I have switched my MetaMask to my second account, then ran the deploy command to
rinkeby
, you can find my transaction here.
💡 The best practice is you version control (git commit) your deployments/
artifacts for discovery, reference and consistency during migrations.
If you are using monorepo like lerna
and yarn workspaces
, then make sure:
-
specified in
lerna.json
in your monorepo root, something like:{ "npmClient": "yarn", "useWorkspaces": true, "packages": ["packages/*"], "version": "independent" }
-
in all of your
packages/*/package.json
, you have specified aversion
field