从零开发区块链应用(十六)

您所在的位置:网站首页 eth代码 从零开发区块链应用(十六)

从零开发区块链应用(十六)

2024-07-10 15:18| 来源: 网络整理| 查看: 265

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

学分: 29 分类: Web3应用 标签: DApp 


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3