iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0
Modern Web

All In One NFT Website Development系列 第 11

Day 11【連動 MetaMask - Pop Up & Login Detection】Can`t use current password.

【前言】
嗨嗨大家好,今天的主題延續昨天的檢測是否已經安裝插件後,緊接著而來的是 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/遠程過程調用

RPC API | MetaMask Docs

先來看看第一個使用例子,是我們等下實作會用到的 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 會儲存一個含有以太坊地址 Stringarray。也就是使用的的以太坊地址。

來看一下第二個使用例子,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


上一篇
Day 10【連動 MetaMask - Login Flow & Extension Check】The strongest password ever.
下一篇
Day 12【連動 MetaMask - Backend & Init】277353
系列文
All In One NFT Website Development30

尚未有邦友留言

立即登入留言