เรียน Golang วันที่ 6 - Ethereum Development with Go

Go Sep 6, 2023

วันนี้ลองเขียน Golang เพื่อทำการสร้าง Wallet Address และลองทำการส่ง ETH ไปให้อีก 1 wallet โดยการใช้ Testnet

พอดีว่าผมมีความสนใจด้าน Blockchain / Ethereum ด้วยส่วนนึง และปกติก็ใช้แค่ ethers.js ในการเป็น Client เชื่อมต่อกับทาง Blockchain ส่วน Node รู้แค่ว่าเค้าน่าจะใจ geth กัน ซึ่งเป็นภาษา Go

วันนี้ก็เลยนั่งลองอ่านบทความคร่าวๆ จากเว็บ Ethereum Development with Go

Introduction · Ethereum Development with Go
Learn how to deploy, compile, interact with smart contracts, send transactions, use the swarm and whisper protocols, and much more with this little guide book on Ethereum Development with Go.

ลองต่อ Public Node ด้วย Go

ผมลองทำการ connect Public RPC ด้วยการใช้เว็บ RPC Info หรือถ้าใครใช้ Local node ก็รันด้วย Foundry Anvil ได้เช่นกัน

RPC Info | Find Blockchain RPCs for any chain
Find over 114 RPCs for any blockchain. RPCs for Web3 development. Mainnet and Testnet RPCs. And quickly add Metamask custom RPCs with a push of a button.
ติดตั้งและลองใช้งาน Foundry
วันนี้ได้ลองหัดเล่น Foundry ซึ่งเคยติดตั้งไว้นานมากๆ แล้ว แต่ไม่ได้ใช้งานซักที ฮ่าๆ วันนี้ลองมานั่งอ่าน Foundry Book นั่งเล่นใหม่ พบว่ามันน่าสนใจมากๆ ตัว Foundry ให้คำนิยามตัวเองไว้ คือ Foundry is a blazing fast,

ส่วน Go หลักๆ ก็เป็น ตัว ethclient ครับ

go mod init go-eth-101

go get github.com/ethereum/go-ethereum/ethclient

ผมมี 2 nodes คือ

  1. Local Node รันด้วย Anvil ได้ URL คือ http://localhost:8545
  2. RPC Node : Sepolia Testnet URL คือ https://rpc.sepolia.org

จากนั้น สร้าง main.go เพื่อลองต่อ RPC Node

package main

import (
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    localNodeUrl := "http://localhost:8545"
    // sepoliaUrl := "https://rpc.sepolia.org"

    client, err := ethclient.Dial(localNodeUrl)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("RPC Connected!")
    _ = client
}

ลองสร้าง New Wallet ด้วย Go

สร้าง function เอาไว้ generate Wallet

package main

import (
    "crypto/ecdsa"
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/crypto"
)

func newWallet() {
  privateKey, err := crypto.GenerateKey()

  if err != nil {
    log.Fatal(err)
  }

  privateKeyBytes := crypto.FromECDSA(privateKey)
  fmt.Println("Private Key : ", hexutil.Encode(privateKeyBytes))

  publicKey := privateKey.Public()
  publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)

  if !ok {
    log.Fatal("error casting public key to ECDSA")
  }

  publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
  fmt.Println("Public Key :", hexutil.Encode(publicKeyBytes)[4:])

  address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
  fmt.Println("Address : ", address)
}

func main() {
    // localNodeUrl := "http://localhost:8545"
    sepoliaUrl := "https://rpc.sepolia.org"

    client, err := ethclient.Dial(sepoliaUrl)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("RPC Connected!")
    _ = client

    newWallet()
}

ลองรัน แล้วได้ผลลัพธ์แบบนี้

RPC Connected!
Private Key :  <PRIVATE_KEY>
Public Key : <PUBLIC_KEY>
Address :  0xEef7020F55A53B1227BE3ACe2514C392c40b8Bfc

ลองเอา Address ไปรับ Faucet ซักนิด

Sepolia Faucet
A fast and reliable Ethereum Sepolia testnet faucet for blockchain developers.

โอน ETH ไปให้อีก Account

ทำการลองโอน ETH จากกระเป๋า ที่สร้างใหม่ กระเป๋า 1 ไปกระเป๋า 2

package main

import (
	"context"
	"crypto/ecdsa"
	"fmt"
	"log"
	"math/big"
	"os"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)

func newWallet() {
	privateKey, err := crypto.GenerateKey()

	if err != nil {
		log.Fatal(err)
	}

	privateKeyBytes := crypto.FromECDSA(privateKey)
	fmt.Println("Private Key : ", hexutil.Encode(privateKeyBytes))

	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)

	if !ok {
		log.Fatal("error casting public key to ECDSA")
	}

	publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
	fmt.Println("Public Key :", hexutil.Encode(publicKeyBytes)[4:])

	address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
	fmt.Println("Address : ", address)
}

// add amount as parameter
func transferTo(to string) {
	// localNodeUrl := "http://localhost:8545"
	sepoliaUrl := "https://rpc.sepolia.io"

	client, err := ethclient.Dial(sepoliaUrl)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("RPC Connected!")

	privateKey, err := crypto.HexToECDSA(os.Getenv("PRIVATE_KEY"))

	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)
	}

	// send 0.1eth (in wei)
	value := big.NewInt(100000000000000000)
	gasLimit := uint64(21000)
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	toAddress := common.HexToAddress(to)
	var data []byte
	tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)

	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		log.Fatal(err)
	}

	err = client.SendTransaction(context.Background(), signedTx)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("tx sent: %s", signedTx.Hash().Hex())

}

func main() {
	// newWallet() // create new wallet, no need client
	transferTo("<YOUR_WALLET>")
}

ทดสอบโอนเงิน 0.1 ETH

PRIVATE_KEY=<YOUR_PRIVATE_KEY> go run main.go

เรียบร้อย เราสามารถโอน ETH ไปให้อีกกระเป๋า ผ่าน go ได้แล้ว 🎉

Source Code

สรุป

วันนี้ได้ลองฝึกใช้ Go แม้ว่าจะไม่ได้เรียนรู้อะไรใหม่ๆ เท่าไหร่ มีเรื่อง context ที่ไม่เข้าใจ ฮ่าๆ แต่ก็ได้รู้วิธีการ generate key และ transfer ETH ถ้าเทียบกัน ส่วนตัวก็ยังรู้สึกว่า ethers.js ใช้งานง่ายกว่า และไม่ยุ่งยาก เพราะ ethers มันจัดการให้หมดแล้ว ส่วน go อันนี้ ต้องมาคำนวณ gas price สร้าง tx มา sign แล้วก็ send tx เองหมดเลย ก็สนุกดี

Happy Coding ❤️

References

Creating Ethereum Wallets in Go
Generating New Wallets · Ethereum Development with Go
Tutorial on how to generate Ethereum wallets with Go.
Transferring ETH · Ethereum Development with Go
Tutorial on how to transfer ETH to another wallet or smart contract with Go.
How to Generate a New Ethereum Address in Go | QuickNode
In this guide, we will cover creating an Ethereum address in Go using the Go-Ethereum Client library.

Tags

Chai Phonbopit

เป็น Web Dev ทำงานมา 10 ปีหน่อยๆ ด้วยภาษา JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจ Web3, Crypto และ Blockchain เขียนบล็อกที่ https://devahoy.com