【前言】
嗨嗨大家好,今天的主題延續昨天的檢測是否已經安裝插件後,緊接著而來的是 MetaMask 的彈出頁面以及檢測登入者是否更換帳戶。今天的內容大部份都參考來自 MetaMask 的使用文件!
【Pop Up & Loading Message】
這裡會跳出 MetaMask 的登入介面。並且在使用者登入時(輸入密碼時)呈現 Loading Message。
使用 useState
初始化 isLoging
查看使用者是否正在輸入密碼,也就是說如果使用者正在登入並且尚未完成登入,都可以歸類在正在輸入的狀態,也就會呈現 Loading 圖示。
export function OnboardingButton() {
const [isLoging, setIsLoging] = useState(false);
// 當 USER 正在 MetaMask 輸入密碼時要呈現 Loading Message
...
return (
<Wrapper>
{ !isLoging && <StyledButton disabled={isDisabled} onClick={onClick}>{buttonText}</StyledButton>}
{ isLoging && <StyledLink to="/login">Welcome Back! Direct to Dino LogIn</StyledLink>}
</Wrapper>
); // 和前面的前端實作一樣都使用邏輯運算子來決定呈現的物件
}
【Ethereum Provider API - ethereum.request
】
在 Ethereum Provider API 中可以使用 ethereum.request
來透過 MetaMask 傳送 RPC 需求給以太坊。在 param
中會利用傳入的各種資訊來調用各種 RPC 方法。而 promise
會儲存所有函式 calling 之後的結果。
interface RequestArguments {
method: string;
params?: unknown[] | object;
}
ethereum.request(args: RequestArguments): Promise<unknown>;
在分散式計算,遠端程序呼叫(英語:Remote Procedure Call,縮寫為 RPC)是一個電腦通信協定。該協定允許執行於一台電腦的程式呼叫另一個位址空間(通常為一個開放網路的一台電腦)的子程式,而程式設計師就像呼叫本地程式一樣,無需額外地為這個互動作用編程(無需關注細節)。RPC是一種伺服器-客戶端(Client/Server)模式,經典實現是一個通過傳送請求-接受回應進行資訊互動的系統。
節錄自維基百科《遠端程序呼叫》- https://zh.wikipedia.org/zh-tw/遠程過程調用
先來看看第一個使用例子,是我們等下實作會用到的 eth_requestAccounts
,主要可以彈出 MetaMask 的登入介面。在 MetaMask 的使用介紹中有特別強調如果這個需求已經被送出,要使按鈕暫時 disable
,這我們待會也會做到。並且盡量建議使用者每次使用時都需要重新登入。
/* eth_requestAccounts -------------------------------------------- */
ethereum
.request({ method: 'eth_requestAccounts' })
.then(handleAccountsChanged)
.catch((error) => {
if (error.code === 4001) {
// EIP-1193 userRejectedRequest error
// The request was rejected by the user
console.log('Please connect to MetaMask.');
} else {
console.error(error);
}
});
在 eth_requestAccounts
這個需求使用後,其 promise
會儲存一個含有以太坊地址 String
的 array
。也就是使用的的以太坊地址。
來看一下第二個使用例子,eth_sendTransaction
可作為創建一個新的調用交易訊息或是用來創建合約。
/* eth_sendTransaction -------------------------------------------- */
params: [
{
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155', // 發送交易的地址
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567', // 交易的目標地址
gas: '0x76c0', // 30400 // gas 可用量,預設是 90000
gasPrice: '0x9184e72a000', // 10000000000000 // gas 價格,預設是 To-Be-Determined
value: '0x9184e72a', // 2441406250 // 交易發送的金額
data: // 合約的編譯代碼
'0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
},
];
ethereum
.request({
method: 'eth_sendTransaction',
params,
})
.then((result) => {
// The result varies by by RPC method.
// For example, this method will return a transaction hash hexadecimal string on success.
})
.catch((error) => {
// If the request fails, the Promise will reject with an error.
});
如果當前送出的 request 因為各種原因失敗的話,將會回傳一個 Ethereum RPC Error。
interface ProviderRpcError extends Error {
message: string;
code: number;
data?: unknown;
}
/* Error Message
- 4001
The request was rejected by the user
- 32602
The parameters were invalid
- 32603
Internal error
*/
【Ethereum Provider API - ethereum.on
】
MetaMask 提供了 Node.js EventEmitter
的 API,並且可以利用 listener
是否被加入來決定是否要進行之後的動作。
Events | Node.js v16.7.0 Documentation
第一個使用例子是 connect
。這個事件將會遞交一個 RPC 要求給鏈,來確認 provider 是否已經連接到鏈上。可以搭配 ethereum.isConnected()
這個方法來檢測當前鏈的連接狀況。
interface ConnectInfo {
chainId: string;
}
ethereum.on('connect', handler: (connectInfo: ConnectInfo) => void);
第二個例子是 disconnect
。這個事件會在無法遞交任何 RPC 要求給任何鏈時出現,在 MetaMask 的使用文件中表示這通常只會出現在網路連接有問題,或其他不可見的錯誤時。當 disconnect
這個事件被遞交後,provider 將不能在接受新的 request
直到 connection
被重新建立。
ethereum.on('disconnect', handler: (error: ProviderRpcError) => void);
要重新建立連接可能需要使用者重新整理頁面,同樣的也可以同時利用 ethereum.isConnected()
這個方法來檢測使用者是否是連接狀態。
第三個例子是 accountsChange
。當 eth_accounts
的 RPC 回傳值改變時這個事件就會被遞交。由於 eth_accounts
回傳的是一個陣列,可能是空或一個地址字串。如果這個地址跟最近使用的 Callers 帳戶不同則會被判斷為改變。在 MetaMask 的使用文件中表示 Callers 的異同是由他們的 URL origin
來判斷,也就是說所有端點都會擁有相同的 origin 並分享同樣的 permissions。
ethereum.on('accountsChanged', handler: (accounts: Array<string>) => {
// Handle the new accounts, or lack thereof.
// "accounts" will always be an array, but it can be empty.
});
Understanding "same-site" and "same-origin"
【連動 MetaMask By React.js】
這裡回到我們的 Project 主軸,利用我們剛剛介紹到的 ethereum.request
彈出 MetaMask 的登入介面,來得到使用者的以太坊帳戶資訊。
export function OnboardingButton() {
...
const onClick = () => {
if (MetaMaskOnboarding.isMetaMaskInstalled()) {
window.ethereum
.request({ method: 'eth_requestAccounts' })
.then((newAccounts) => setAccounts(newAccounts));
} else {
onboarding.current.startOnboarding();
}
};
return (
...
);
}
使用 useEffect
隨時檢測是否更換帳戶以及如果有的話就更新地址資料。
export function OnboardingButton() {
...
useEffect(() => {
function handleNewAccounts(newAccounts) {
setAccounts(newAccounts);
}
if (MetaMaskOnboarding.isMetaMaskInstalled()) {
window.ethereum
.request({ method: 'eth_requestAccounts' })
.then(handleNewAccounts);
window.ethereum.on('accountsChanged', handleNewAccounts);
return () => {
window.ethereum.off('accountsChanged', handleNewAccounts);
};
}
}, []);
...
return (
...
);
}
而我們現在就可以成功地透過 accounts[0]
來取用登入者的地址!
ethereum.on('accountsChanged', function (accounts) {
// Time to reload your interface with accounts[0]!
console.log(accounts[0])
});
【小結】
今天的內容有一點多,但大部分都是 MetaMask 使用文件有提到的說明,所以只是加以翻譯並且套用,並沒有非常困難,嗎。
【參考資料】
JSON RPC · ethereum/wiki Wiki
json-rpc
MetaMask-GitHub