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


<<:  {DAY11} SQL查询语法3

>>:  Day 11 - 阵列 a

[DAY11] Data Access Layer 设计概念

前言 这篇将介绍 boxenn 与 DAL 层的依赖关系和介面。 简易 Class Diagram ...

【领域展开 05 式】 WordPress.org 与 WordPress.com,你好.org

WordPress.org 与 WordPress.com,试用时完全不知道有分这两个 在前几天的文...

IT 铁人赛 k8s 入门30天 -- day14 K8s Services explained

前言 这篇文章主要来介绍 Service 元件 内容会谈到 Service 的功能与不同种 Serv...

Day7-JDK查看正在运行的Java进程工具:jps

前言 在介绍JDK有哪些工具时,第二大列应该是『故障排查、分析、监控和管理工具』,但我想先从监控工具...

【从零开始的Swift开发心路历程-Day24】天气预报App实作Part3

昨天我们已经能把单一地点的天气资讯显示到手机App上面了,接着我们会利用UIButton让我们可以选...