从零开发区块链应用(十六)--ETH转账处理

ETH转账处理

一、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转账处理


点赞 2
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
杰哥的技术杂货铺
杰哥的技术杂货铺
0x6e60...2aa2
添加微信:Jemooner ,备注:【登链社区读者】即可加入读者交流群