Skip to content

Commit

Permalink
feat: complete faucet demo
Browse files Browse the repository at this point in the history
  • Loading branch information
smallfu6 committed Dec 22, 2024
1 parent 8a99680 commit e1f1e47
Show file tree
Hide file tree
Showing 14 changed files with 488 additions and 15 deletions.
32 changes: 32 additions & 0 deletions chain/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package chain

import (
"log"

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

var EthClient *ethclient.Client
var RpcClient *rpc.Client

var rpcUrl = "https://eth-sepolia.g.alchemy.com/v2/YQHOAbPqR8tHixcwD-ZW5xxUtVjbEmHA"

// var rpcUrl = "https://mainnet.infura.io/v3/672e64bfa6f144349608236513a79679"

func init() {
ethClient, err := ethclient.Dial(rpcUrl)
if err != nil {
log.Fatal(err)
}

EthClient = ethClient

// rpc
rpcClient, err := rpc.Dial(rpcUrl)
if err != nil {
log.Fatalf("Failed to connect to the Ethereum rpc: %v", err)
}

RpcClient = rpcClient
}
166 changes: 166 additions & 0 deletions chain/transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package chain

import (
"context"
"crypto/ecdsa"
"encoding/hex"
"errors"
"faucet/model"
"fmt"
"math/big"
"strings"
"time"

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

func SendTransaction(transaction model.Transaction) (string, error) {
privateKey, err := crypto.HexToECDSA(transaction.SK)
if err != nil {
// log.Fatal(err)
return "", err
}

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
// log.Fatal("error casting public key to ECDSA")
return "", errors.New("error casting public key to ECDSA")
}

fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := EthClient.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
// log.Fatal(err)
return "", err
}

chainID, err := EthClient.NetworkID(context.Background())

// 使用types.NewTx创建EIP-1559类型的交易
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
GasTipCap: transaction.MaxPriorityFeePerGas, // 设置合适的 MaxPriorityFeePerGas
GasFeeCap: transaction.MaxFeePerGas, // 设置合适的 MaxFeePerGas
Gas: transaction.GasLimit, // 设置合适的 Gas Limit
To: transaction.ContractAddress,
Value: transaction.Value,
Data: transaction.Data,
})

// 签名交易
signedTx, err := types.SignTx(tx, types.NewLondonSigner(chainID), privateKey)
if err != nil {
// log.Fatal(err)
return "", err
}

err = EthClient.SendTransaction(context.Background(), signedTx)
if err != nil {
// log.Fatal(err)
return "", err
}

return signedTx.Hash().Hex(), nil
}

// TODO:
// MQ提高稳定性
// DB数据持久化
func CheckTransaction(tx string) (bool, error) {
txHash := common.HexToHash(tx)
time.Sleep(10 * time.Second)

var checkcount = 0

// 轮询检查交易是否被确认
for {
time.Sleep(5 * time.Second)
checkcount++
if checkcount >= 20 {
return false, nil
}
time.Sleep(10 * time.Second)
_, isPending, err := EthClient.TransactionByHash(context.Background(), txHash)
if err != nil {
fmt.Println("Error getting transaction: ", err)
errInfo := fmt.Sprintf("%s", err.Error())
if strings.Contains(errInfo, "not found") {
continue
}
fmt.Println("CheckTransaction TransactionByHash err: ", err)
return false, err
}

if !isPending {
receipt, err := EthClient.TransactionReceipt(context.Background(), txHash)
if err != nil {
errInfo := fmt.Sprintf("%s", err.Error())
if strings.Contains(errInfo, "not found") {
continue
}
// log.Fatalf("Error getting transaction receipt: %v", err)

fmt.Println("CheckTransaction TransactionReceipt err: ", err)
return false, err
}

if receipt.Status == types.ReceiptStatusSuccessful {
// fmt.Println("Transaction confirmed!")
return true, nil
} else {
// fmt.Println("Transaction failed!")
return false, nil
}
}

}
}

func GetGasPrice() (*big.Int, *big.Int, *big.Int, error) {
var block map[string]interface{}
err := RpcClient.CallContext(context.Background(), &block, "eth_getBlockByNumber", "latest", false)
if err != nil {
// panic(err)
return big.NewInt(0), big.NewInt(0), big.NewInt(0), err
}
baseFeePerGasStr := block["baseFeePerGas"].(string) // 获取baseFeePerGas为字符串
baseFeePerGas := new(big.Int)
baseFeePerGas, ok := baseFeePerGas.SetString(baseFeePerGasStr[2:], 16) // 从十六进制转换为big.Int
if !ok {
// panic("Failed to parse baseFeePerGas")
return big.NewInt(0), big.NewInt(0), big.NewInt(0), errors.New("Failed to parse baseFeePerGas")
}

// 设置最大优先费用(这里示例设置为2 Gwei)
maxPriorityFeePerGas := big.NewInt(2e9) // 2 Gwei
// fmt.Printf("Max Priority Fee Per Gas: %s\n", maxPriorityFeePerGas.String())

// 设置最大费用,假设为基本费用的2倍加上最大优先费用
maxFeePerGas := new(big.Int).Mul(baseFeePerGas, big.NewInt(3))
maxFeePerGas = maxFeePerGas.Add(maxFeePerGas, maxPriorityFeePerGas)

return baseFeePerGas, maxPriorityFeePerGas, maxFeePerGas, nil
}

func GetGasLimit(fromAddress, toAddress *common.Address, data []byte, value *big.Int) uint64 {
// 构造交易数据
tx := map[string]interface{}{
"from": fromAddress,
"to": toAddress,
"data": "0x" + hex.EncodeToString(data),
"value": hexutil.EncodeBig(value),
}

var gasLimit hexutil.Uint64
if err := RpcClient.CallContext(context.Background(), &gasLimit, "eth_estimateGas", tx); err != nil {
fmt.Printf("Failed to estimate gas: %v", err)
}

fmt.Printf("Estimated Gas: %d\n", gasLimit)
return uint64(gasLimit)
}
36 changes: 36 additions & 0 deletions chain/transfer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package chain

import (
"faucet/model"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/spf13/viper"
)

func Transfer(receive string, value *big.Int) (string, error) {
var trans model.Transaction

_, MaxPriority, MaxFee, err := GetGasPrice()
trans.MaxPriorityFeePerGas = MaxPriority
trans.MaxFeePerGas = MaxFee
to := common.HexToAddress(receive)
trans.ContractAddress = &to
trans.Value = value

sk := viper.GetString("faucet.private_key")
trans.SK = sk

trans.GasLimit = 21000

tx, err := SendTransaction(trans)
if err != nil {
return "", err
}

go CheckTransaction(tx)

fmt.Println("提交转账交易: ", tx)
return tx, nil
}
12 changes: 6 additions & 6 deletions config/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package config
import (
"fmt"
"log"
"os"

"github.com/spf13/viper"
"gorm.io/driver/postgres"
Expand All @@ -14,14 +13,15 @@ var DB *gorm.DB

func ConnectDB() {
dbHost := viper.GetString("database.host")
dbPort := os.Getenv("database.port")
dbUser := os.Getenv("database.user")
dbPassword := os.Getenv("database.password")
dbName := os.Getenv("database.dbname")
dbSsl := os.Getenv("database.sslmode")
dbPort := viper.GetString("database.port")
dbUser := viper.GetString("database.user")
dbPassword := viper.GetString("database.password")
dbName := viper.GetString("database.dbname")
dbSsl := viper.GetString("database.sslmode")

pgi := "host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=Asia/Shanghai"
dsn := fmt.Sprintf(pgi, dbHost, dbUser, dbPassword, dbName, dbPort, dbSsl)

db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to the database:", err)
Expand Down
4 changes: 3 additions & 1 deletion config/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
"github.com/spf13/viper"
)

func InitConfig() {
func init() {
viper.SetConfigName("config") // 配置文件名称(不带扩展名)
viper.SetConfigType("yaml") // 配置文件格式
viper.AddConfigPath("./config") // 配置文件路径

if err := viper.ReadInConfig(); err != nil {
panic(fmt.Errorf("Fatal error config file: %w \n", err))
}

ConnectDB()
}
33 changes: 33 additions & 0 deletions controller/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package controller

import (
"fmt"
"math/big"
)

type RequestFaucet struct {
Address string `json:"address" binding:"required"`
Amount string `json:"amount" binding:"required"`
TokenSymbol string `json:"token_symbol" binding:"required"`
ChainID string `json:"chain_id" binding:"required"`
}

func ethToWei(ethAmount string) (*big.Int, error) {
fmt.Println(ethAmount)

// 将 ETH 转换为 Wei
amountFloat, ok := new(big.Float).SetString(ethAmount)
if !ok {
return nil, fmt.Errorf("invalid amount format: %s", ethAmount)
}

// 1 ETH = 10^18 wei
// 使用 big.Float 来处理乘法操作,保持精度
weiFloat := new(big.Float).Mul(amountFloat, big.NewFloat(1e18)) // ETH -> wei

// 转换为 big.Int,注意这里是通过 Int() 方法将 big.Float 转换为 big.Int
weiInt, _ := weiFloat.Int(nil)

// 这样 weiInt 会是一个精确的整数表示
return weiInt, nil
}
Loading

0 comments on commit e1f1e47

Please sign in to comment.