主页 > imtoken转账怎么取消 > 在元掩码中创建多个帐户计为一个或多个

在元掩码中创建多个帐户计为一个或多个

imtoken转账怎么取消 2023-08-06 05:12:43

一、发展目标

在这个开发之前,作者修改了网络选择按钮中每个按钮颜色的实现,放弃了自定义Theme的实现,改为直接给图标设置颜色。这里是描述,具体代码不再列出。另外,前面开发中提到的Provider实际上是指Context,特此更正描述。

在上一次开发中,我们拼接了钱包的主界面,可以实时正确显示用户的余额。这次我们在钱包主界面完成发送ETH功能。我们先登录钱包,切换到 Kovan 测试网:

在这里插入图片描述

你也可以先切换到kovan测试网再登录,都一样。进入主界面后,会自动获取用户余额。在获取余额之前,发送按钮是灰色的,无法点击。点击发送按钮,我们打算显示一个这样的简单页面:

在这里插入图片描述

这里还是可以切换网络的,再次切换后会自动获取用户余额,不是Send before get是不可点击的。单击取消按钮返回主钱包界面。我们输入一个账户地址,点击发送,如果验证通过,会出现一个确认框:

在这里插入图片描述

用户可以再次检查输入是否有误,点击确认后,加载一段时间后会出现如下界面:

在这里插入图片描述

该界面会显示本次转账的简要信息,EtherScan Link上也有提供查看详情。注意:Testnet 交易也可以在 EtherScan 上查看。稍等片刻后,交易状态会自动变为 Completed,您可以随时点击返回按钮直接返回主界面。注意:本钱包开发方案不具备保存交易记录功能,点击返回后将无法看到此交易信息。

提示

在中国无法直接访问 EtherScan,您可能需要梯子。但是,如果是主网交易,也可以在 EtherScan.CN 上查看,不需要梯子。这是 EtherScan.CN 链接:

二、React 状态改善

在之前的开发中,我们在钱包主界面实时获取并更新了用户的账户余额。可以看出,在本次开发中,发送表单界面也有同样的需求,因为用户切换网络后必须获取并更新对应网络的账户余额。这样,平衡和平衡的修改需要在两个地方共享。根据 React 的设计原则,需要对状态进行升级,将原本在钱包主界面中实现的余额实时更新升级为两个界面的公共父组件(元素)。这里为了降低复杂度,我们将用户余额从 GlobalProvider.js 中分离出来,并将其和更新余额的实现放在一个特殊的 Context 中。先删除对应的代码,然后新建src\contexts\BalancesProvider.js,代码如下:

/**
*  本文件用来实时更新用户的余额
*/
import React, { createContext, useEffect, useContext, useReducer, useMemo, useCallback } from 'react'
import { ethers } from 'ethers'
import { safeAccess } from '../utils'
import {useGlobal} from './GlobalProvider'
const UPDATE='UPDATE'
const BalancesProvider = createContext()
function useBalancesContext() {
  return useContext(BalancesProvider)
}
function reducer(state,{type,payload}) {
    switch (type) {
        case UPDATE:{
            const {address,network,value} = payload
            return {
                ...state,
                [address]:{
                    ...(safeAccess(state,[address]) || {}),
                    [network]:{
                        value
                    }
                }
            }
        }
        default:{
          throw Error(`Unexpected action type in BalancesContext reducer: '${type}'.`)
        }
    }
}
export default function Provider({children}) {
    const [state, dispatch] = useReducer(reducer, {})
    const {wallet,network} = useGlobal()
    const update = useCallback((address,network,value) => {
        dispatch({type:UPDATE, payload:{address,network,value}})
    },[])
    //刷新每个账号在每个网络下的余额
    useEffect(()=>{
        if(wallet) {
            const {address} = wallet
            let stale = false
            const provider = ethers.getDefaultProvider(network)
            provider.on(address, value => {
                if(!stale ){
                   update(address,network,value)
                }
            });
            return ()=>{
                stale = true
                provider.removeAllListeners(address)
            }
        }
    },[wallet,network,update])
    return (
        <BalancesProvider.Provider value={useMemo(() => [state, { update }], [state, update])}>
            {children}
        </BalancesProvider.Provider>
    )
}
export function useBalance(address,network) {
    const [state,] = useBalancesContext()
    const {value} = safeAccess(state,[address,network]) || {}
    return value
}

这段代码记录了每个网络中的用户账户(测试网和测试网的主要ETH余额)并实时更新。当然,只有切换到某个网络时,该网络的账户余额才会实时更新,当前未选择的网络不会实时更新。

前面开发中提到了Context的作用,后面会介绍。以下是这段代码中的三个关键点:

useReducer 的用法。替代使用状态。它采用 (state, action) => newState 形式的 reducer,并返回当前状态及其调度方法。一般在 Context 中使用较多的是 useReducer。详见:钱包实时更新方法。使用 useEffect 中的 ethers 库监听用户余额变化事件,注意其依赖和返回函数。 useEffect的详细用法:最后导出的useBalance是一个自定义的钩子,用来获取用户的余额。它有账户地址和网络两个参数,也就是说它支持多账户和多网络余额的获取和更新,虽然目前在我们的开发计划中,钱包是单账户钱包。 三、React 数据流

我们借助发送按钮点击后的逻辑实现来介绍React的数据流向。 React 与 Vue 不同,它的数据是从上到下的单向流,每个组件只能改变自己的数据(状态),而 Vue 是双向流。 React 是这样设计的比特币地址批量生成,一方面是为了降低复杂性,另一方面是为了快速定位问题,因为状态只能由拥有它的组件更改。双向流也有它的优点。这里不比较谁好谁差,只介绍React的设计。

新建一个src\views\SendEther.jsx,代码如下:

/**
*  本文件用来实现钱包主界面发送ETH功能
*/
import React,{useState} from 'react';
import { withRouter } from "react-router";
import SendEtherForm from './SendEtherForm'
import TransactionInfo from './TransactionInfo'
//交易状态
const BEGIN = 'begin'
const PENDING = 'pending'
const values_init = {
    status:BEGIN,
    tx:null,            //交易HASH
    td:null             //交易结果
}
function SendEther({history}) {
    const [values,setValues] = useState(values_init)
    //交易发送之后转到交易信息页面
    const sendOver = tx => {
        setValues({
            status:PENDING,
            tx,
            td:null
        })
    }
    //返回主界面
    const resverseBack = () => {
        history.push('/detail')
    }
    const {status,tx} = values
    if(status === BEGIN){
        return (
            <SendEtherForm cancelCallback={resverseBack} sendCallback={sendOver} />
        )
    }else if(status === PENDING) {
        return (
            <TransactionInfo tx={tx} reverseCallback={resverseBack} />
        )
    }else {
        return null
    }
}
export default withRouter(SendEther)

注意这段代码:它会转换组件的数据(Status)tx是通过TransactionInfo组件的属性tx传递的,即数据是通过属性从上往下传递的,属性不能改变了。因此比特币地址批量生成,如果传输的数据发生了变化,则必须修改原始组件。

但是,我们有时希望子组件可以通过一些操作来修改父组件的数据(状态)。我们应该做什么?我们来看这段代码:它做了一个修改父组件状态的方法sendOver作为回调函数,通过属性sendCallback传递给组件SendEtherForm。这样,SendEtherForm 组件在 ETH 转账交易发送后执行 sendCallback 回调,并会调用父组件对应的 sendOver 方法来修改父组件的状态。修改状态后,父组件会重新渲染,显示交易信息界面,而不是转账表单界面。

在React中,无论是传递状态还是反向修改状态,父组件的状态或修改方法都必须通过属性逐级向下传递,这样当组件树的层级较多时, 会导致一些中间组件有这些属性不是很有意义。为了避免这种情况,我们可以使用上下文。 Context 提供了一种在组件树之间传递数据的方法,而无需手动向每个级别的组件添加 props。但是,Context 必须在使用前进行初始化,一般在组件树的顶层。

Context详细教程:

四、实现ETH转账

在以太坊中,ETH转账也是一笔交易,交易的一般描述为:一个外部账户(用户,非合约)发起一笔交易(创建一个交易对象),然后用私钥,并等待以太坊上的矿工打包、执行并发布到所有节点。如果中间有操作改变以太坊的状态,就会消耗gas。同时,您还需要设置一个gas price。消耗的gas量乘以gas价格就是您的交易费用,将从您的余额中扣除。 Gas 的价格也决定了交易的速度。如果您支付的价格很高,您将成为 VIP 通道。如果你付出的价格低,就没有矿工来对付你,你只能慢慢等或者根本不等。我们也可以在交易中发送 ETH,它也会从您的余额中扣除。如果执行过程中出现错误(或gas不足),您的交易将失败,所有由此产生的更改将被重置,并且发送的ETH将被退回,但费用将部分或全部消耗,具体取决于错误的实际情况。如果矿工执行交易并将其发布到所有节点,则您的交易成功并永久存储在以太坊上。

在具体实现中,首先要构造一个交易对象来执行转账。在 JavaScript 中,事务对象当然是一个普通的对象,例如 {},它具有以下可选属性:

以上七个属性都是可选的,也就是说可以省略,当然不能全部省略。

在src\views\SendEtherForm\index.js中,构造交易对象的代码块为:

let transaction = {
    to:_address,
    value:utils.parseEther("" + eth_amount),
    gasLimit:GAS_LIMIT,
    gasPrice:utils.parseUnits("" + gas_price,'gwei'),
    chainId:getChainIdByNetwork(_network)
}

可以看到对比上面的七个属性,我们没有nonce和data,我们只转ETH,没有交易数据,所以没有数据。随机数让它自己决定。

签署和发送交易的代码块是:

//签名并发送交易
let provider = ethers.getDefaultProvider(network)
let tx_wallet = wallet.connect(provider)
let sendPromise = tx_wallet.sendTransaction(transaction);
setOpen(false)
setCircleOpen(true)
sendPromise.then(tx => {
    setCircleOpen(false)
    if(sendCallback){
        sendCallback(tx)
    }
}).catch(err =>{
    setCircleOpen(false)
    return showSnackbar("ETH发送失败,请检查你的余额")
})

前两行代码连接我们的钱包和要交易的网络Link,第三行代码我们使用钱包对象的sendTransaction方法来签署和发送交易,注意它返回一个promise。交易发送后,我们会得到一个交易对象tx,其中包含交易的hash、nonce、receiver等信息。可以看到,我们通过回调函数将事务对象 tx 传递给父组件(反向更改数据流中的数据)。

交易对象tx有一个wait()方法,用于等待交易的执行结果(矿工打包执行和释放的结果),并且还返回了一个promise,我们来看看它的用法,在src\views\TransactionInfo\index.js中,代码块为:

useEffect(()=>{
    if(tx){
        tx.wait().then(td => {
            setState({
                pendingOver:true,
                td
            })
        }).catch(err => {})
    }
},[tx])

tx.wait() 返回一个包含交易结果的td,我们可以用它来显示一些执行信息,比如转账是否成功。请注意,tx.wait() 的执行时间是不确定的,取决于网络拥塞和矿工的选择。当一笔交易被签名发送但没有返回结果时,交易的状态为pending,字面意思是pending。交易结果返回后,状态为成功或失败。

我们的交易信息界面中还使用了三个进度条来动态指示待处理。有兴趣的读者可以看看进度条的用法。最后放一个转账完成的界面:

在这里插入图片描述

这是点击界面上的EtherScan查看超链接后在EtherScan上的结果:

在这里插入图片描述

五、总结

在本次开发中,我们主要完成了更新用户账户余额的状态改进和用户转账功能的实现。同时也介绍了以太坊上交易的一般流程和交易对象的构建方法。最后简单介绍一下交易签名发送和执行的返回结果。具体的UI代码就不介绍了,可以直接在码云git仓库查看或下载代码。

接下来的开发计划会实现账号导出功能。

本钱包码云git仓库地址:=>

请留言指正或提出宝贵意见和建议。