ETH转账处理
在本章节中,你将学习如何将ETH从一个帐户转移到另一个帐户。如果你已熟悉以太坊,那么你就知道如何交易包括你打算转账的以太币数量量,燃气限额,燃气价格,一个随机数(nonce),接收地址以及可选择性的添加的数据。 在广告发送到网络之前,必须使用发送方的私钥对该交易进行签名。
假设你已经连接了客户端,下一步就是加载你的私钥。在本课程中,你将学习如何将ETH从一个帐户转移到另一个帐户。如果你已熟悉以太坊,那么你就知道如何交易包括你打算转账的以太币数量量,燃气限额,燃气价格,一个随机数(nonce),接收地址以及可选择性的添加的数据。 在广告发送到网络之前,必须使用发送方的私钥对该交易进行签名。
假设你已经连接了节点客户端,下一步就是获取from地址的私钥。
// 生成资金池的私钥
pri, _ := wallet.NewMasterKey("")
该函数需要我们发送的帐户的公共地址 - 这个我们可以从私钥派生。
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
如果我们已确认from地址,则可以直接定义一个变量就可以,从私钥中派生出from地址判断是否一致即可
//定义from地址
from := "0xa2fd8293e0e29286650c235d736606c31f422428"
// 生成资金池的私钥
pri, _ := wallet.NewMasterKey("")
// 判断地址是否相等
// 判断用户地址 与 使用user_id生成的地址是否一致,如果不是,则返回false
if addr != wallet.NewAccount(userid) {
return false
}
下一步是设置我们将要转移的ETH数量。 但是我们必须将ETH以太转换为wei,因为这是以太坊区块链所使用的。 以太网支持最多18个小数位,因此1个ETH为1加18个零。 这里有一个小工具可以帮助您在ETH和wei之间进行转换: https://etherconverter.netlify.com
value, _ := new(big.Int).SetString(amount, 10) // in wei (1 eth)
ETH转账的燃气应设上限为“21000”单位。
gas, _ := new(big.Int).SetString("210000", 10) // in units
燃气价格必须以wei为单位设定。 在撰写本文时,将在一个区块中比较快的打包交易的燃气价格为50 gwei。
gasPrice, _ := new(big.Int).SetString("5 0000000000", 10) // in wei (50 gwei)
然而,燃气价格总是根据市场需求和用户愿意支付的价格而波动的,因此对燃气价格进行硬编码有时并不理想。 go-ethereum客户端提供SuggestGasPrice函数,用于根据'x'个先前块来获得平均燃气价格。
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
接下来我们弄清楚我们将ETH发送给谁。
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
或者直接接收用户的传参
tostr := addr
to := ecdsa.HexToAddress(toStr)
之后我们需要获得帐户的随机数(nonce)。 每笔交易都需要一个nonce。 根据定义,nonce是仅使用一次的数字。 如果是发送交易的新帐户,则该随机数将为“0”。 来自帐户的每个新事务都必须具有前一个nonce增加1的nonce。很难对所有nonce进行手动跟踪,于是ethereum客户端提供一个帮助方法GetTransactionCount,它将返回你应该使用的下一个nonce。
首先将nonce值进行加锁,防止交易并发
// 将nonce值进行加锁
nonce.Lock.Lock()
// 代码执行后,解锁nonce值
defer nonce.Lock.Unlock()
接下来我们可以读取我们应该用于帐户交易的随机数
// 获取当前from地址的nonce值
if true {
nonce.Number, err = rc.Client.GetTransactionCount(from)
}
if err != nil {
return "", fmt.Errorf("SendRawTransactionNonce s.GetNonce err:%v", err.Error())
}
// nonce := uint64(11)
现在我们最终可以通过导入go-ethereumcore/types包并调用NewTransaction来生成我们的未签名以太坊事务,这个函数需要接收nonce,地址,值,燃气上限值,燃气价格和可选发的数据。 发送ETH的数据字段为“nil”。 在与智能合约进行交互时,我们将使用数据字段,仅仅转账以太币是不需要数据字段的。
以下将用if判断该交易为普通转账还是合约转账
//判断to地址长度是否为0000711*987456.001447745632110.
//判断to地址是否为0
if len(toStr) == 0 {
//如果data为空
//如果to地址为0,data值为空,则返回报错
if data == nil {
return "", fmt.Errorf("SendRawTransactionNonce data parameter error")
}
//to地址为0,data不为空,说明该交易为合约交易,则对以太坊交易进行签名
tx = NewContractCreation(nonce.Number, value, gasLimit, gasPrice, data)
} else {
tx = NewTransaction(nonce.Number, to, value, gasLimit, gasPrice, data)
}
//to地址不为0,,说明该交易为普通交易,则对以太坊交易进行签名
tx, err = SignTx(tx, NewHubbleSigner(rc.ChainID), priKeyBuf)
if err != nil {
return "", fmt.Errorf("SendRawTransaction SignTx err:%v", err.Error())
}
下一步是针对已签名的交易进行rlp序列化。
// rlp序列化
txParam, err := rlp.EncodeToBytes(tx)
if err != nil {
return "", fmt.Errorf("SendRawTransaction rlp.EncodeToBytes tx err:%v", err.Error())
}
接下来调用交易发送的方法
info, err := rc.Client.SendContract(conv.ToHex(txParam))
if err != nil {
logger.Error("SendRawTransaction", "step", "SendContract", "nonce", nonce.Number, "err", err.Error())
return "", err
}
res, ok := info.(string)
if !ok {
logger.Error("SendRawTransaction", "step", "HexToString", "err", err.Error())
return "", err
}
现在我们终于准备要将已签名的事务广播到整个网络。
// SendContract 发送合约
func (eth *Http) SendContract(data string) (interface{}, error) {
args = []interface{}{data}
params := NewHttpParams("eth_sendRawTransaction", args)
resBody, err := eth.rpc.HttpRequest(params)
if err != nil {
return nil, err
}
return eth.ParseJsonRPCResponse(resBody)
}
用户调用转账的方法
func tranfer(userid, addr, amount string) bool {
from := "0xa2fd8293e0e29286650c235d736606c31f422428"
to := addr
// 生成资金池的私钥
pri, _ := wallet.NewMasterKey("")
// 判断地址是否相等
// 判断用户地址 与 使用user_id生成的地址是否一致,如果不是,则返回false
if addr != wallet.NewAccount(userid) {
return false
}
//将字符串类型的值转换为10进制的整数类型
gas, _ := new(big.Int).SetString("100000", 10)
gasPrice, _ := new(big.Int).SetString("5000000000", 10)
value, _ := new(big.Int).SetString(amount, 10)
input := []byte("0x0")
result, err := rc.SendRawTransaction(from, to, pri.ToHex(), value, gas, gasPrice, input)
if err != nil {
logger.Error("tranfer", "step", "SendRawTransaction", "addr", addr, "err", err)
return false
}
logger.Warn("tranfer", "step", "result", "addr", addr, "hash", result)
model.UserWalletUpdateMOBA(userid, weiToEth(amount))
return true
}
交易签名方法
// SendRawTransaction 发送签名交易
func (rc *RequestChain) SendRawTransaction(from, toStr, hexPriKey string, value, gas, gasPrice *big.Int, data []byte) (string, error) {
// 预执行交易GetEstimateGas
/*inputStr := conv.ToHex(data)
valueStr := strconv.FormatInt(value.Int64(), 16)
logger.Info("GetEstimateGas", "inputStr", inputStr, "valueStr", valueStr)
mayGas, err := rc.Client.GetEstimateGas(from, toStr, conv.ToHex(data), "0x"+strconv.FormatInt(value.Int64(), 16))
if err != nil {
return "", fmt.Errorf("GetEstimateGas run err:%v", err.Error())
}
// 使用的gas
spareGas := mayGas + mayGas/3
if gas.Int64() < spareGas {
gas = big.NewInt(spareGas)
}*/
// 获取私钥
// 获取私钥,将字符串转为byte
priKeyBuf, err := hex.DecodeString(hexPriKey)
if err != nil {
return "", fmt.Errorf("SendRawTransactionNonce parameter hexPriKey err:%v", err.Error())
}
// 判断nonce值
// 将nonce值进行加锁
nonce.Lock.Lock()
// 代码执行后,解锁nonce值
defer nonce.Lock.Unlock()
// 获取当前from地址的nonce值
if true {
nonce.Number, err = rc.Client.GetTransactionCount(from)
}
if err != nil {
return "", fmt.Errorf("SendRawTransactionNonce s.GetNonce err:%v", err.Error())
}
// nonce := uint64(11)
//将string类型的to地址转为一个对象
to := ecdsa.HexToAddress(toStr)
var gasLimit uint64 = 0
if nil == gas {
gasLimit = 0
} else {
gasLimit = gas.Uint64()
}
// Construct transaction object
var tx *Transaction
//判断to地址长度是否为0000711*987456.001447745632110.
//判断to地址是否为0
if len(toStr) == 0 {
//如果data为空
//如果to地址为0,data值为空,则返回报错
if data == nil {
return "", fmt.Errorf("SendRawTransactionNonce data parameter error")
}
//to地址为0,data不为空,说明该交易为合约交易,则对以太坊交易进行签名
tx = NewContractCreation(nonce.Number, value, gasLimit, gasPrice, data)
} else {
tx = NewTransaction(nonce.Number, to, value, gasLimit, gasPrice, data)
}
//to地址不为0,,说明该交易为普通交易,则对以太坊交易进行签名
tx, err = SignTx(tx, NewHubbleSigner(rc.ChainID), priKeyBuf)
if err != nil {
return "", fmt.Errorf("SendRawTransaction SignTx err:%v", err.Error())
}
// rlp序列化
txParam, err := rlp.EncodeToBytes(tx)
if err != nil {
return "", fmt.Errorf("SendRawTransaction rlp.EncodeToBytes tx err:%v", err.Error())
}
info, err := rc.Client.SendContract(conv.ToHex(txParam))
if err != nil {
logger.Error("SendRawTransaction", "step", "SendContract", "nonce", nonce.Number, "err", err.Error())
return "", err
}
res, ok := info.(string)
if !ok {
logger.Error("SendRawTransaction", "step", "HexToString", "err", err.Error())
return "", err
}
return res, nil
}
广播以太坊交易方法
// SendContract 发送合约
func (eth *Http) SendContract(data string) (interface{}, error) {
args = []interface{}{data}
params := NewHttpParams("eth_sendRawTransaction", args)
resBody, err := eth.rpc.HttpRequest(params)
if err != nil {
return nil, err
}
return eth.ParseJsonRPCResponse(resBody)
}
本系列文章: 从零开发区块链应用(一)--golang配置文件管理工具viper 从零开发区块链应用(二)--mysql安装及数据库表的安装创建 从零开发区块链应用(三)--mysql初始化及gorm框架使用 从零开发区块链应用(四)--自定义业务错误信息 从零开发区块链应用(五)--golang网络请求 从零开发区块链应用(六)--gin框架使用 从零开发区块链应用(七)--gin框架参数获取 从零开发区块链应用(八)--结构体初识 从零开发区块链应用(九)--区块链结构体创建 从零开发区块链应用(十)--golang协程使用 从零开发区块链应用(十一)--以太坊地址生成 从零开发区块链应用(十二)--以太坊余额查询 从零开发区块链应用(十三)--以太坊区块查询 从零开发区块链应用(十四)--以太坊交易哈希查询 从零开发区块链应用(十五)--以太坊交易匹配查询 从零开发区块链应用(十六)--ETH转账处理
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!