Find the code on github.com/the-real-blockchin.
Our work will be done in two programming languages: JavaScript and Solidity.
JavaScript should be familiar as the language that was originally intended to run in the browser. It is an completely dynamically typed scripting language that has classes using the prototype model (different than the template model of most OOL's). Node.js is a server-side runtime environment for JavaScript that features asynchronous programming. Asynchronous programming means that (in particular) I/O calls are not blocking, but will return immediately with a Promise object. Learning requires a bit of study.
We will use the JS library ethers.js to interact with an etherium blockchain. There are three cases of blockchains,
Ethereum contracts are described by Ethereum Virtual Machine EVM. EVM is bytecode and we do not write it directly. The high level Ethereum language we will use is Solidity, and it compiles to EVM.
We will use the Hardhat development environment. Hardhat is a Node package which automates the creation, deployment and testing of Solidity contracts. It uses convention over configuration, with a directory structure and configuration files determined by the Hardhat environment.
Begin with the installation of npm given in the hardhat setup. Once done, test that node is installed by running it interactively at the command line.
% node --version v22.4.1 % node Welcome to Node.js v22.4.1. Type ".help" for more information. > 1+1 2 > const multiply = (x, y) => x * y; undefined > multiply(3,4) 12 > const my_vars = { a:1, b:2 } undefined > const {a} = my_vars undefined > a 1 > .exit %
% npm init % npm install --save-dev hardhat % npx hardhat init --> select Create an empty hardhat.config.js % npm install --save-dev @nomicfoundation/hardhat-toolbox
and add
require("@nomicfoundation/hardhat-toolbox");to your hardhat.config.js file, to include the toolbox library.
We pause our Ethereum steps to connect up our project directory with a github git repository. You can follow the github tutorial on Software Carpentry.
% echo "# hello-ethereum" >> README.md % echo ".DS_store" >> .gitignore % echo "artifacts" >> .gitignore % git init % git add README.md % git add .gitignore % git commit -m "initial commit" % git branch -M main % git remote add origin __your_repo_dot_git__ % git push -u origin main
You may need to set your username and email, and git might ask for your password. Your password is the classic token that you created on github, and will need to be renewed.
git config --global user.name "__username__" git config --global user.email "__email__" git config --global credential.helper storeThere are two ways to connect to the github site: by classic token and by ssh. The classic token is essentially a password authentication. However instead of user chosen password, a long random string is generated and rights on the repository are attached to the token. It is used when a password is asked for.
Cavet Emptor: I have not tested this. Ssh is a bit more convenient, in that it uses all the ssh machinery to connect to github. You create a public/private key pair, upload the public key to github, and set your .ssh/config as so,
Host github User git Hostname github.com IdentityFile ~/.ssh/__my_ssh_private_key__and then rather than https://__repo_name__ use git@github/__repo_name__.
// contracts/HelloWorld.sol // SPDX-License-Identifier: UNLICENSED // ^ means 0.9 etc also acceptable (~ means 0.8.25 also acceptable) pragma solidity ^0.8.24; contract HelloWorld { string private _greeting = "Hello World!" ; address private _owner ; constructor () { _owner = msg.sender ; } modifier onlyOwner() { require ( msg.sender == _owner, "Ownable: caller is not the owner" ) ; _ ; } function getGreeting() external view returns(string memory) { return _greeting ; } function setGreeting(string calldata greeting) external onlyOwner { _greeting = greeting ; } function owner() public view returns(address) { return _owner ; } }
The compile this code with,
npx hardhat compileIf this works, add, commit and git push the contract file.
Note that the contract bytecode is in the artifacts directory, which we have set in .gitignore, as I wish to only repo source code.
To run a contract costs ether, and ether costs money. The actual powering is gas and the price of gas in ether is market determined. If the contract cannot be completed because it runs out of gas, the state is reverted and the ether charged. If the contract completes without using all of the given gas, the ether is credited back.
There are three ways will will run on an ethereum blockchain. The easiest is on a local blockchain created by Hardhat. To run a blockchain localling, open a new termina winddow and use the command,
npx hardhat nodeand keep the window open. Closing the window will terminate the blockchain and loose all of the acquired state.
When Hardhat starts up a local node it creates wallets for several accounts and fills them with test ether. Note in the console the address of the wallerts and their private keys.
To connect a Node script with any blockchain we will need a library. Install ethers.js as,
npm install --save-dev @nomicfoundation/hardhat-ethers ethersand then add this line to the top of hardhat.config.js,
require("@nomicfoundation/hardhat-ethers");For reference see this turtorial.
We will need the following script to deploy our contract,
// scripts/deploy.js async function main () { // We get the contract to deploy const HelloWorld = await ethers.getContractFactory('HelloWorld'); console.log('Deploying HelloWorld ...'); const hello_world = await HelloWorld.deploy(); await hello_world.waitForDeployment(); console.log('HelloWorld deployed to:', await hello_world.getAddress()); } main() .then(() => process.exit(0)) .catch(error => { console.error(error); process.exit(1); });
Note the use of async and await. Node supports asynchronous programming where tasks are launched in threads. Returning from any such launch is an object the represents the perhaps ongoing execution. This is particularly relevant in ethereum because execution on the blockchain are by volunteer nodes, and the results have to be checked and finalized, and the time for this to occur depends.
The script is placed in the script directory and then run,
% npx hardhat run --network localhost ./scripts/deploy.js Deploying HelloWorld ... HelloWorld deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
The result, if successful, with the the address on the local blockchain of the contract instance.
Then get to a console that can connect with the node, to exercise the contract,
% npx hardhat console --network localhost Welcome to Node.js v22.4.1. Type ".help" for more information. > await ethers.provider.getBlockNumber() 1 > const HW = await ethers.getContractFactory('HelloWorld') undefined > const hw = await HW.attach("0x5fbdb2315678afecb367f032d93f642f64180aa3") undefined > await hw.getGreeting() 'Hello World!' > await hw.setGreeting('Goodnight Moon') ContractTransactionResponse { provider: HardhatEthersProvider { _hardhatProvider: LazyInitializationProviderAdapter { _providerFactory: [AsyncFunction (anonymous)], _emitter: [EventEmitter], _initializingPromise: [Promise], provider: [BackwardsCompatibilityProviderAdapter] }, _networkName: 'localhost', _blockListeners: [], _transactionHashListeners: Map(0) {}, _eventListeners: [] }, blockNumber: 2, blockHash: '0x6f288b0fdfcb56173bef9294240cfb31912793a1ccb5e6ec46ed6ea3f01c33b1', index: undefined, hash: '0xbe27e55d179ba7632cb262c26639ef0ba22dce9e475687c84ca682f7848f8b24', type: 2, to: '0x5FbDB2315678afecb367f032d93F642f64180aa3', from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', nonce: 1, gasLimit: 30000000n, gasPrice: 973870221n, maxPriorityFeePerGas: 232421875n, maxFeePerGas: 973870221n, maxFeePerBlobGas: null, data: '0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e68656c6c6f20657468657269756d000000000000000000000000000000000000', value: 0n, chainId: 31337n, signature: Signature { r: "0x83e432c71291c5f53a6bb27d38d3cff4649555c7344c0e1b2068ebc5cc0bf308", s: "0x75960700254ce1e815c938e02b5949e2987f6ded906926049b3401496700c9f7", yParity: 0, networkV: null }, accessList: [], blobVersionedHashes: null } > await ethers.provider.getBlockNumber() 2 > await hw.getGreeting() 'Goodnight Moon' > .exit %
author: burton rosenberg
created: 1 aug 2024
update: 20 sep 2024