邊學邊用--使用React下的Material UI框架開發一個簡單的仿MetaMask的網頁版以太坊錢包(九)

一、上期回顧與本期計劃

       在上一期開發中,我們實現了ERC20代幣轉賬的功能。這次開發,我們按計劃實現簽名交易的功能。我在上一章提到過,因爲我們的錢包是一個網頁版的錢包,所以實現簽名交易的方式很有些醜陋。用戶只能通過查詢字符串將交易對象的參數發過來然後進行解析再簽名交易。簽名交易和ETH轉賬的本質其實是相同的,都是先構造一個交易對象,然後使用私鑰簽名這個交易對象。關於交易對象怎麼構造,我已經寫了一篇 Js中構建以太坊交易對象詳解 ,歡迎大家有空先看一下。

       假定我們的錢包運行在localhost:3000,(也就是開發服務器)。如果我們提供如下的url:
http://localhost:3000/transfer?data=0x07391dd6000000000000000000000000000000000000000000000000000000000000000a&to=0x0cbde7fbf0f97726b804135fa638a86ceecae633&chainId=42

       錢包會解析查詢字符串並構建一個交易對象,然後使用私鑰簽名發送交易。當然了,在解析完成之後用戶必須先登錄。下面是本次開發的簽名交易對象頁面:
在這裏插入圖片描述
       點擊確認,交易發出後就會進入簡要交易信息界面:
在這裏插入圖片描述
       這裏再點擊返回按鈕就退回到錢包主界面。

二、功能實現

2.1 UI的實現

       UI的界面參照了MetaMask的界面,拼起來有些繁瑣,重點有如下幾點:

       1、是flex佈局的運用。flex佈局中用的最多的是元素的對齊,包括主軸和副軸上的對齊。主軸對齊用justifyContent屬性,一般設置爲space-between或者center。而副軸使用alignItems屬性,一般設置爲center。如下列代碼片斷:

const useStyles = makeStyles(theme => ({
    container: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
    },
    containerHeader: {
        display: 'flex',
        justifyContent: 'space-between'
    },
}));

       2、另一個重點是位置屬性,需要根據情況靈活設置,這裏簡要介紹一下位置屬性。在CSS中,一個元素的position屬性有四種值:static(默認值)fixed(固定值)absolute(絕對的)relative(相對的)

  • 靜態的 靜態意味使用正常流佈局。靜態並沒有被位置化,所以它不會受top left bottom right和z-index的影響。
  • 固定的 固定位置的元素最容易懂,它總是顯示並且根據窗口進行位置計算。因爲我們的錢包只佔web中間一部分大小,所以整個錢包未使用固定位置元素。
  • 相對位置 幾乎和靜態位置一樣,它也遵循正常的佈局流。不過它可以使用top left bottom right等位置參數。
  • 絕對位置 和固定位置類似 ,不過它也是根據最近一個非static的元素來確定位置的。

       這其中相對位置元素也可以給絕對位置元素提供一個基點,因爲相對位置是非static的。我們在錢包中爲了調整地址Icon和地址的高度,就使用了相對位置。代碼片斷爲:

address:{
    position:'relative',
    marginLeft:theme.spacing(1),
    top:theme.spacing(0.5),
},

還有一點常用的設置是padding和margin,這個只要是前端開發都已經掌握了。

2.2 代碼邏輯的要點

       這一次的開發比較簡單,除了UI拼接複雜外,代碼邏輯都是在錢包已經有的內容上做一些修改就完成了。主要是增加了兩個全局變量用來記錄當前構造的交易對象和交易響應,另外一點是使用查詢字符串來獲取足夠的信息以構建交易對象。

2.2.1 查詢字符串中值的獲取

在React中獲取url中查詢字符串的方法有多種,本錢包中是這樣獲取的:
1、使用withRouter來包裝輸出組件,主要是爲了提供location屬性

export default withRouter(SignTransaction)

2、函數組件中使用location屬性

function SignTransaction({history,location}) {
}

3、使用URLSearchParams來獲取查詢字符串,它返回的是一個查詢對象,獲得具體屬性值要使用get方法

let search = location.search
let query = new URLSearchParams(search)
let transaction_info = convertQuery(query)
......
updateGlobal({
    transaction:transaction_info
})

2.2.2 交易對象的構建

       這個讀者可以先閱讀我在本文開頭提到的那篇文章,當然如果讀者已經很熟練,可以跳過那篇文章。

const {isLogin,transaction,wallet} = useGlobal()
let trans = {
    ...transaction,
    gasPrice:utils.parseUnits("" + price,'gwei'),
    value:utils.parseEther("" + (transaction.value || 0)),
}

       因爲我們可以讓用戶重新設置gasPrice,所以這裏對gasPrice進行了替換。另外爲了簡化,我們需要做一個約定。使用url查詢字符串發過來的值都是10進制的(不是16進制),並且都是常用單位。所以查詢字符串中value=0.01而不是value=10000000000000000。同樣,gasPrice的值也是以Gwei爲單位的數字。chainId的值是十進制數字,而不是網絡名,因爲它只有1,3,4,42這幾個,很容易記住。注意:data的值必須爲16進制字符串,且以’0x’開頭。

2.2.3 交易的發送

       交易的簽名發送和ETH轉賬相同,只是交易對象多了幾個屬性值。交易提交後還需要進行一些路由導航處理。

setCircleOpen(true)
//簽名併發送交易
let provider = ethers.getDefaultProvider(net)
let tx_wallet = wallet.connect(provider)
tx_wallet.sendTransaction(trans).then(tx => {
    setCircleOpen(false)
    //todo 跳到交易信息界面
    updateGlobal({
        txGlobal:{
            tx,
            symbol:(!trans.data || trans.data==='0x') ? "ETH" : '',
            status:'pending',
        },
        transaction:null,
    })
    history.push('/send')
}).catch(err =>{
    setCircleOpen(false)
    return showSnackbar("交易發送失敗",'error')
})

       代碼片斷中,先打開一個進度條動畫代表正在發送交易。當sendTransaction返回時,關閉這個進度條。接着再設置全局變量txGlobal,同時將交易對象重置爲null,防止再次進入交易界面(此時已經不需要transaction了,交易已經發出去了)。最後再導航到SendEther.jsx。我們來看SendEther.jsx中的代碼片斷:

const {txGlobal} = useGlobal()
const [values,setValues] = useState({
    ...values_init,
    ...txGlobal
})
const {status,tx,amount,symbol} = values
if(status === BEGIN){
    return (
        <SendEtherForm cancelCallback={resverseBack} sendCallback={sendOver} />
    )
}else if(status === PENDING) {
    return (
        <TransactionInfo tx={tx} amount={amount} symbol={symbol} reverseCallback={resverseBack} />
    )
}else {
    return null
}

       因爲我們在導航之前已經將status設置成了’pending’,所以它不會再顯示發送界面,而是顯示交易信息界面。

2.2.4 transfer路由的處理

       因爲我們的簽名交易界面和其它界面不同,沒有錢包的頭部,所以我們需要更改路由比較的位置,在更早的位置比較transfer,而不是在錢包主體那裏比較。這是src\views\Main.jsx中的代碼片斷:

import SignTransaction from './SignTransaction'
....
<Router >
    <Switch>
        <Route path='/transfer' component={SignTransaction} />
        <Route path='/'>
             <WalletBar />
             <Routes />
        </Route>
    </Switch>
</Router>

       從這裏我們可以看出,如果匹配了transfer,直接跳到對應頁面;如果不是,則先顯示錢包標題欄(就是logo和網絡切換按鈕),然後主體的內容再根據路由再次匹配。

三、一點小經驗

       本次開發內容不多,主要是UI的拼接。這裏有一點個人小經驗:如果你感覺對哪個div或者其它元素沒有底,可以將它的背景顏色設置一個較淺的顏色,這樣你可以方便看到它的大小和形狀、範圍等。或者你也可以打開Chrome開發者工具也能看到,如下圖:
在這裏插入圖片描述
       點擊開發者工具裏的Elements,你就可以看到整個HTML節點樹,展開其中的節點並點擊其中一個div,你會在左邊看到一個藍色背景標記的塊,它就代表這個div,同時在右邊會顯示它的marginborderpadding等。

四、小結

       本次開發主要完成了錢包簽名交易的實現。通過將交易的對象屬性值以查詢字符串的方式傳遞給網頁,再由網頁解析後進行交易。由於網頁版本的限制,每次簽名都必須打開一個新的頁面,這是它的不足。因爲我們開發這個錢包的主要目的是學習它能做什麼事,而不是它能做的多好,以學習爲主。

       雖然錢包主界面裏有一個交易記錄,但是目前並未計劃開發。有興趣的讀者可以先自己嘗試一下,主要是將每次交易提交後的狀態保存下來(包括是否完成、交易hash等),保存在對應賬號對應的網絡下,同樣也使用本地存儲。登錄錢包後再去重新獲取這些交易的狀態並更新。

       本次錢包計劃中的功能已經全部開發完成了。由於我們開發時是一個功能一個功能開發的,相互之間缺乏整體設計和協調性,因此計劃再用一些時間進行codereview。主要是優化或者簡化代碼, 同時將它打包後發佈在網站上。

本錢包碼雲(gitee)倉庫地址爲: => https://gitee.com/TianCaoJiangLin/khwallet

歡迎讀者留言指出錯誤或者提出改進意見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章