Deploy smart contract and connect with go Ethereum
In this article, I am going to deploy a smart contract to go and go-Ethereum on the local block chain.
Go-ethereum
Go, a programming language developed by Google, is one of the languages used to build the Ethereum network, and Go Ethereum is the interface used to write, edit and manipulate the Go code for the blockchain. Aside from Go, the Ethereum blockchain and protocol are also heavily based on Python and C++.
USDT smart contract
Proof-of-work (PoW)
The Ethereum network began by using a consensus mechanism that involved Proof-of-work (PoW). This allowed the nodes of the Ethereum network to agree on the state of all information recorded on the Ethereum blockchain and prevented certain kinds of economic attacks. However, Ethereum switched off proof-of-work in 2022 and started using proof-of-stake instead.
Proof-of-work has now been deprecated. Ethereum no longer uses proof-of-work as part of its consensus mechanism. Instead, it uses proof-of-stake. Read more on proof-of-stake and staking.
Proof-of-stake (PoS)
Proof-of-stake (PoS) underlies Ethereum’s consensus mechanism. Ethereum switched on its proof-of-stake mechanism in 2022 because it is more secure, less energy-intensive, and better for implementing new scaling solutions compared to the previous proof-of-work architecture.
Proof-of-stake is a way to prove that validators have put something of value into the network that can be destroyed if they act dishonestly. In Ethereum’s proof-of-stake, validators explicitly stake capital in the form of ETH into a smart contract on Ethereum. The validator is then responsible for checking that new blocks propagated over the network are valid and occasionally creating and propagating new blocks themselves. If they try to defraud the network (for example by proposing multiple blocks when they ought to send one or sending conflicting attestations), some or all of their staked ETH can be destroyed.
Get project
git clone https://github.com/owanhunte/ethereum-solidity-course-updated-code.git
cd lottery
node -v
v18.19.0
npm init
lottery.sol file
pragma solidity >=0.5.0 <0.9.0;
contract Lottery {
address public manager;
address payable[] public players;
constructor() {
manager = msg.sender;
}
function enter() public payable {
require(msg.value >= .01 ether, "A minimum payment of .01 ether must be sent to enter the lottery");
players.push(payable(msg.sender));
}
function random() private view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.prevrandao, block.number, players)));
}
function pickWinner() public onlyOwner {
uint256 index = random() % players.length;
address contractAddress = address(this);
players[index].transfer(contractAddress.balance);
players = new address payable[](0);
}
function getPlayers() public view returns (address payable[] memory) {
return players;
}
modifier onlyOwner() {
require(msg.sender == manager, "Only owner can call this function.");
_;
}
}
Install dependency
npm install solc
npm install @truffle/hdwallet-provider
npm install --save mocha ganache-cli web3
npm update
npm install solc@0.8.23 //deploy on infura
We need to change in package.json
"scripts": {
"test": "mocha"
},
Run the tests
npm run test
Deploy Contract
set .env file
ACCOUNT_MNEMONIC="neutral ... "
GOERLI_ENDPOINT="https://sepolia.infura.io/v3/code"
Run the deploy.js
node deploy.js
Result
Attempting to deploy from account 0x6e06Dc176Ee86DCb3C428a42Ed9AAeD9c0Eb1935
Contract deployed to 0xc7e367A2EaE77148459b048ba3a3f6288EB82832
TXhash:
0xe87abc8cf10bde4395220b2c2eed29e378be3f425719354dc1c65ced9a7dd03d
Go-Ethereum
popularly referred to as Geth, is the official Ethereum client for building decentralized applications using the Go programming language. Geth is one of the preferred alternatives for running, setting up nodes, and interacting with Ethereum blockchains due to its ease of use.
go get github.com/ethereum/go-ethereum
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
var (
contractAddress = common.HexToAddress("0xc7e367A2EaE77148459b048ba3a3f6288EB82832")
myAddress = common.HexToAddress("0x6e06Dc176Ee86DCb3C428a42Ed9AAeD9c0Eb1935")
)
func main() {
client, err := ethclient.Dial("https://sepolia.infura.io/v3/code")
if err != nil {
log.Fatal(err)
}
networkID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(networkID)
balance, err := client.BalanceAt(context.Background(), contractAddress, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance)
myBalance, err := client.BalanceAt(context.Background(), myAddress, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(myBalance)
}
You can connect to the infura gateway if you don’t have an existing client. Infura manages a bunch of Ethereum [geth and parity] nodes that are secure, reliable, scalable and lowers the barrier to entry for newcomers when it comes to plugging into the Ethereum network.
client, err := ethclient.Dial("https://mainnet.infura.io")
Using Ganache
Ganache is an Ethereum simulator that makes developing Ethereum applications faster, easier, and safer. It includes all popular RPC functions and features (like events) and can be run deterministically to make development a breeze.
You must first install Node.js >= v16.0.0 and npm >= 7.10.0.
sudo npm install -g ganache-cli
ganache
ganache-cli
Connect to Ganache
client, err := ethclient.Dial("http://localhost:8545")
if err != nil {
log.Fatal(err)
}
ganache-cli -m "much repair shock carbon improve miss forget sock include bullet interest solution"
Download UI Ganache
https://trufflesuite.com/ganache/
wget https://github.com/trufflesuite/ganache-ui/releases/download/v2.7.1/ganache-2.7.1-linux-x86_64.AppImage
chmod u+x <AppImage File>
./exampleName.AppImage
cd ~; ./ganache-2.7.1-linux-x86_64.AppImage
dlopen(): error loading libfuse.so.2
sudo apt install libfuse2
Golang codes
address = "http://127.0.0.1:7545"
client, err := ethclient.Dial(address)
if err != nil {
log.Fatal(err)
}
Deploy on Ganache-cli
GOERLI_ENDPOINT="http://localhost:8545"
ganache-cli -m "neutral ..."
node deploy.js
Attempting to deploy from account 0x6e06Dc176Ee86DCb3C428a42Ed9AAeD9c0Eb1935
[
'0x6e06Dc176Ee86DCb3C428a42Ed9AAeD9c0Eb1935'
]
Contract deployed to 0xf6e2551d2AFaFBA200D3803aE6a72B515Fa9f825
In Ganache Terminal
Transaction: 0x40fb2033ec7b4c8a9c95945d7f3b83f9b73405eb43723cc7e4729d12a225b673
Contract created: 0xf6e2551d2afafba200d3803ae6a72b515fa9f825
Gas usage: 646841
Block number: 1
Block time: Mon Dec 18 2023 16:58:05 GMT
Account Balances
Reading the balance of an account is pretty simple; call the BalanceAt
method of the client passing it the account address and optional block number. Setting nil
as the block number will return the latest balance.
var (
contractAddress = common.HexToAddress("0xf6e2551d2afafba200d3803ae6a72b515fa9f825")
myAddress = common.HexToAddress("0x6e06Dc176Ee86DCb3C428a42Ed9AAeD9c0Eb1935")
)
account := common.HexToAddress(myAddress)
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 999997816911625000000
Get balance on blockchain 1
blockNumber := big.NewInt(1)
balance, err = client.BalanceAt(context.Background(), myAddress, blockNumber)
if err != nil {
log.Fatal(err)
}
Because we’re dealing with big numbers we’ll have to import the native Go math and math/big packages
Pending balance
Sometimes you’ll want to know what the pending account balance is, for example after submitting or waiting for a transaction to be confirmed. The client provides a similar method to BalanceAt
called PendingBalanceAt
which accepts the account address as a parameter.
pendingBalance, err := client.PendingBalanceAt(context.Background(), myAddress)
fmt.Println(pendingBalance) // 25729324269165216042
Block header
You can call the client’s HeaderByNumber
to return header information about a block. It'll return the latest block header if you pass nil
.
header, err := client.HeaderByNumber(context.Background(), nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(header.Number.String()) // 1
Full block
Call the client’s BlockByNumber
method to get the full block. You can read all the contents and metadata of the block such as block number, block timestamp, block hash, block difficulty, as well as the list of transactions and much much more.
Count Block
count, err := client.TransactionCount(context.Background(), block.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(count)
Transaction data
for _, tx := range block.Transactions() {
fmt.Println(tx.Hash().Hex())
fmt.Println(tx.Value().String())
fmt.Println(tx.Gas())
fmt.Println(tx.GasPrice().Uint64())
fmt.Println(tx.Nonce())
}
___________________________Transaction
0x40fb2033ec7b4c8a9c95945d7f3b83f9b73405eb43723cc7e4729d12a225b673
0
646841
4500000000
0
Smart Contract Compilation & ABI
Solc is available as a snapcraft package for Ubuntu.
sudo snap install solc --edge
We also need to install a tool called abigen
for generating the ABI from a solidity smart contract.
go get -u github.com/ethereum/go-ethereum
cd $GOPATH/src/github.com/ethereum/go-ethereum/
make
make devtools
Abigen
Abigen is a binding-generator for easily interacting with Ethereum using Go. Abigen creates easy-to-use, type-safe Go packages from Ethereum smart contract definitions known as ABIs. This abstracts away a lot of the complexity of handling smart contract deployment and interaction in Go native applications such as encoding and decoding smart contracts into EVM bytecode. Abigen comes bundled with Geth. A full Geth installation includes the abigen binary. Abigen can also be built independently by navigating to go-ethereum/cmd/abigen and running go build, or equivalently:
$ cd $GOPATH/src/github.com/ethereum/go-ethereum
$ go build ./cmd/abigen
Use smart contract in Golang
Make ABI from contract.
solc --abi Lottery.sol
======= Lottery.sol:Lottery =======
Contract JSON ABI
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"enter","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getPlayers","outputs":[{"internalType":"address payable[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"manager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pickWinner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"players","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
Or build in file :
solc --abi Storage.sol -o build
The contract binding can then be generated by passing the ABI to abigen as follows:
abigen --abi ./build/Lottery.abi --pkg main --type Lottery --out Lottery.go
In order to deploy a smart contract from Go, we also need to compile the solidity smart contract to EVM bytecode. The EVM bytecode is what will be sent in the data field of the transaction. The bin file is required for generating the deploy methods on the Go contract file.
solc --bin Lottery.sol
======= Lottery.sol:Lottery =======
Binary:
608060405234801561000f575f80fd5b50335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610a7f8061005c5f395ff3fe608060405260043610610049575f3560e01c8063481c6a751461004d5780635d495aea146100775780638b5b9ccc1461008d578063e97dcb62146100b7578063f71d96cb146100c1575b5f80fd5b348015610058575f80fd5b506100616100fd565b60405161006e919061054c565b60405180910390f35b348015610082575f80fd5b5061008b610120565b005b348015610098575f80fd5b506100a16102c5565b6040516100ae919061062d565b60405180910390f35b6100bf610350565b005b3480156100cc575f80fd5b506100e760048036038101906100e29190610684565b6103fc565b6040516100f491906106be565b60405180910390f35b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146101ad576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a490610757565b60405180910390fd5b5f6001805490506101bc610437565b6101c691906107a2565b90505f309050600182815481106101e0576101df6107d2565b5b905f5260205f20015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8273ffffffffffffffffffffffffffffffffffffffff163190811502906040515f60405180830381858888f19350505050158015610261573d5f803e3d5ffd5b505f67ffffffffffffffff81111561027c5761027b6107ff565b5b6040519080825280602002602001820160405280156102aa5781602001602082028036833780820191505090505b50600190805190602001906102c092919061046b565b505050565b6060600180548060200260200160405190810160405280929190818152602001828054801561034657602002820191905f5260205f20905b815f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116102fd575b5050505050905090565b662386f26fc1000034101561039a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103919061089c565b60405180910390fd5b600133908060018154018082558091505060019003905f5260205f20015f9091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6001818154811061040b575f80fd5b905f5260205f20015f915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f4443600160405160200161044e939291906109ea565b604051602081830303815290604052805190602001205f1c905090565b828054828255905f5260205f209081019282156104e1579160200282015b828111156104e0578251825f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091602001919060010190610489565b5b5090506104ee91906104f2565b5090565b5b80821115610509575f815f9055506001016104f3565b5090565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6105368261050d565b9050919050565b6105468161052c565b82525050565b5f60208201905061055f5f83018461053d565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6105988261050d565b9050919050565b6105a88161058e565b82525050565b5f6105b9838361059f565b60208301905092915050565b5f602082019050919050565b5f6105db82610565565b6105e5818561056f565b93506105f08361057f565b805f5b8381101561062057815161060788826105ae565b9750610612836105c5565b9250506001810190506105f3565b5085935050505092915050565b5f6020820190508181035f83015261064581846105d1565b905092915050565b5f80fd5b5f819050919050565b61066381610651565b811461066d575f80fd5b50565b5f8135905061067e8161065a565b92915050565b5f602082840312156106995761069861064d565b5b5f6106a684828501610670565b91505092915050565b6106b88161058e565b82525050565b5f6020820190506106d15f8301846106af565b92915050565b5f82825260208201905092915050565b7f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f5f8201527f6e2e000000000000000000000000000000000000000000000000000000000000602082015250565b5f6107416022836106d7565b915061074c826106e7565b604082019050919050565b5f6020820190508181035f83015261076e81610735565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6107ac82610651565b91506107b783610651565b9250826107c7576107c6610775565b5b828206905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f41206d696e696d756d207061796d656e74206f66202e3031206574686572206d5f8201527f7573742062652073656e7420746f20656e74657220746865206c6f7474657279602082015250565b5f6108866040836106d7565b91506108918261082c565b604082019050919050565b5f6020820190508181035f8301526108b38161087a565b9050919050565b5f819050919050565b6108d46108cf82610651565b6108ba565b82525050565b5f81549050919050565b5f81905092915050565b5f819050815f5260205f209050919050565b6109098161058e565b82525050565b5f61091a8383610900565b60208301905092915050565b5f815f1c9050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61096261095d83610926565b610931565b9050919050565b5f6109748254610950565b9050919050565b5f600182019050919050565b5f610991826108da565b61099b81856108e4565b93506109a6836108ee565b805f5b838110156109dd576109ba82610969565b6109c4888261090f565b97506109cf8361097b565b9250506001810190506109a9565b5085935050505092915050565b5f6109f582866108c3565b602082019150610a0582856108c3565b602082019150610a158284610987565b915081905094935050505056fea264697066735822122002a23570c68d06ef9b64bc5066fb949367588412ac467a89d0e3d8098007384864736f6c637829302e382e32342d646576656c6f702e323032332e31322e31352b636f6d6d69742e3162356336663636005a
Deploying contracts to Ethereum
solc --bin Lottery.sol -o Lottery.bin
Then abigen can be run again, this time passing Storage.bin:
abigen --abi ./build/Lottery.abi --pkg main --type Lottery --out Lottery.go --bin ./Lottery.bin/Lottery.bin
The new DeployLottery() function can be used to deploy the contract to an Ethereum testnet from a Go application. To do this requires incorporating the bindings into a Go application that also handles account management, authorization and Ethereum backend to deploy the contract through. Specifically, this requires:
- A running Geth node connected to an Ethereum testnet
- An account in the keystore prefunded with enough ETH to cover gas costs for deploying and interacting with the contract
Use Private key in Ganache-cli
ganache-cli -m "neutral ..."
address := "http://localhost:8545" // ganache cli
client, err := ethclient.Dial(address)
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("872e066455dcfb1e38e1c6109f3522f2d2344e3b58fbfbe0b47803c971784760")
if err != nil {
log.Fatal(err)
}
nonce, err := client.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(privateKey.PublicKey))
if err != nil {
log.Fatal(err)
}
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println("gasPrice: ", gasPrice)
chainID, err := client.ChainID(context.Background())
fmt.Println("networkID :", chainID)
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
if err != nil {
fmt.Println(err)
}
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) // in wei
auth.GasLimit = uint64(300000) // in units
auth.GasPrice = gasPrice
txAddress, tx, instance, err := contracts.DeployLottery(auth, client)
if err != nil {
fmt.Println("DeployLottery")
log.Fatal(err.Error())
}
fmt.Println(instance, address, tx, instance)
fmt.Println(txAddress.Hex()) // 0x147B8eb97fD247D06C4006D269c90C1908Fb5D54
fmt.Println(tx.Hash().Hex())
fmt.Println(txAddress.Hex())
fmt.Println(tx.Hash().Hex())
0xf6e2551d2AFaFBA200D3803aE6a72B515Fa9f825
0x81ea1d8c480a316f5721edf31a3c1f39f3827b5cc75e9b6fcad928c3e261dd08
Transaction: 0x81ea1d8c480a316f5721edf31a3c1f39f3827b5cc75e9b6fcad928c3e261dd08
Contract created: 0xf6e2551d2afafba200d3803ae6a72b515fa9f825
Gas usage: 300000
Block number: 1
Block time: Tue Dec 19 2023 14:10:57
Runtime error: code size to deposit exceeds maximum code size
Loading a Smart Contract
Once you’ve compiled your smart contract’s ABI to a Go package using the abigen
tool, the next step is to call the "New" method, which is in the format New<ContractName>
, so in our example if you recall it's going to be NewStore. This initializer method takes in the address of the smart contract and returns a contract instance that you can start interact with it.
Transaction: 0x25b79f942ebccfae067bb94a0e76282e7c1c2db825e5cbee18da1bb487596e06
address := "http://localhost:8545" // ganache cli
client, err := ethclient.Dial(address)
if err != nil {
log.Fatal(err)
}
contractAddress := common.HexToAddress("0x25b79f942ebccfae067bb94a0e76282e7c1c2db825e5cbee18da1bb487596e06")
instance, err := contracts.NewLottery(contractAddress, client)
if err != nil {
log.Fatal(err)
}
Writing to a Smart Contract
Contract transaction:
Transaction: 0x25b79f942ebccfae067bb94a0e76282e7c1c2db825e5cbee18da1bb487596e06
Use privates keys in ganache:
address := "http://localhost:8545" // ganache cli
client, err := ethclient.Dial(address)
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("872e066455dcfb1e38e1c6109f3522f2d2344e3b58fbfbe0b47803c971784760")
if err != nil {
log.Fatal(err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(gasPrice)
chainID, err := client.ChainID(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println("networkID :", chainID)
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
if err != nil {
log.Fatal(err)
}
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) // in wei
auth.GasLimit = uint64(3000000) // in units
auth.GasPrice = gasPrice
contractAddress := common.HexToAddress("0x25b79f942ebccfae067bb94a0e76282e7c1c2db825e5cbee18da1bb487596e06")
balance, err := client.BalanceAt(context.Background(), contractAddress, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("balance : ", balance)
myAddress := common.HexToAddress("0x6e06Dc176Ee86DCb3C428a42Ed9AAeD9c0Eb1935")
myBalance, err := client.BalanceAt(context.Background(), myAddress, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("myBalance : ", myBalance)
instance, err := contracts.NewLottery(contractAddress, client)
if err != nil {
fmt.Println("NewLottery")
log.Fatal(err)
}
tx, err := instance.Enter(auth)
if err != nil {
fmt.Println("Enter")
log.Fatal(err)
}
go run *.go
2000000000
networkID : 1337
balance : 0
myBalance : 999997000000000000000
Transaction: 0xa531650fe246012062408f17e4d56ace4622a4680ac799279262c60666201fe5
Gas usage: 21064
Block number: 6
Block time: Tue Dec 19 2023 15:18:07 GMT