赞
踩
对 metamask 的介绍和应用场景,见官方文档,写的很好。其实这也是节点交互的一个很好的学习材料。
metamask 会提供一个可以通过
window.ethereum
的全局API,可以:请求连接账户、获取用户连接的链的数据(如交易后的返回值和事件)、以及显示用户对交易的签名状态。 provider 的存在可以显示以太坊用户的对象。
我们可以通过检查 window.ethereum
是否定义,来检查浏览器中是否运行运行 metamask
- if (typeof window.ethereum !== 'undefined') {
- console.log('MetaMask is installed!');
- }
如果要和其他兼容的钱包区分,则需要使用
ethereum.isMetaMask
,返回布尔值。我们常用 ganache 来模拟区块链,实现快速的开发,尤其是可以
ganache-cli -m "助记词"
生成特定的用户和地址,很方便与 metamask 配合。注意:由于交易的 nonce 机制,当我们重启了私链后,可能会出现冲突,导致交易阻塞,我们需要在设置-高级设置-重置账户。
注意:申请与 metamask 连接的按钮必须显示提供,不能自动申请连接,这是规范。
对于任何DApp,都必须首先:
- // 检测钱包
- import detectEthereumProvider from '@metamask/detect-provider';
-
- // 返回 provider
- const provider = await detectEthereumProvider();
-
- if (provider) {
- startApp(provider);
- } else {
- console.log('Please install MetaMask!');
- }
-
- function startApp(provider) {
- // 检测prover是否完整,未被更改
- if (provider !== window.ethereum) {
- console.error('Do you have multiple wallets installed?');
- }
- }
-
- //获取链Id
- const chainId = await ethereum.request({ method: 'eth_chainId' });
- handleChainChanged(chainId); //重新加载页面并且触发事件
-
- ethereum.on('chainChanged', handleChainChanged);
-
- function handleChainChanged(_chainId) {
- // 建议刷新页面,因为连接的节点不同了。
- window.location.reload();
- }
-
- //改变连接的用户
- let currentAccount = null;
- ethereum
- .request({ method: 'eth_accounts' }) //返回的是一个数组
- .then(handleAccountsChanged)
- .catch((err) => {
- console.error(err);
- });
-
-
- ethereum.on('accountsChanged', handleAccountsChanged);
-
- function handleAccountsChanged(accounts) {
- if (accounts.length === 0) {
- // 未连接钱包或者钱包锁定了
- console.log('Please connect to MetaMask.');
- } else if (accounts[0] !== currentAccount) {
- currentAccount = accounts[0]; //切换账户
- }
- }
-
- //访问账户
-
- // 应该,点击按钮,再连接钱包。
- //
- // 如果获取用户失败,应该让用户重新点击按钮连接。
- document.getElementById('connectButton', connect);
-
- // 等待用户确定连接时,应该禁用申请访问账户的按钮,因为必须确认后才能进行下面操作
- function connect() {
- ethereum
- .request({ method: 'eth_requestAccounts' })
- .then(handleAccountsChanged)
- .catch((err) => {
- if (err.code === 4001) {
- // 用户拒绝连接
- console.log('Please connect to MetaMask.');
- } else {
- console.error(err);
- }
- });
- }
链ID编号:
0x1 | 1 | Ethereum Main Network (Mainnet) |
---|
ethereum.isMetaMask
:检查是否存在 metamask 提供的provider.
ethereum.isConnected()
:当前的 provider 是否连接到开放 json-rpc 的节点(能否发送请求),但是这在切换网络时可能有延迟。
ethereum.request(args)
:这是发起请求的主要办法,它返回的是 promise
- interface RequestArguments {
- method: string;
- params?: unknown[] | object;
- }
-
- ethereum.request(args: RequestArguments): Promise<unknown>;
参数和返回值会根据方法的改变而改变。如果请求失败会返回 error 的对象
例子:
- params: [
- {
- from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
- to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
- gas: '0x76c0', // 30400
- gasPrice: '0x9184e72a000', // 10000000000000
- 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.
- });
provider 提供了 Node.js 的 EventEmitter,下面是常见的事件。
- interface ConnectInfo {
- chainId: string;
- }
- ethereum.on('connect', handler: (connectInfo: ConnectInfo) => void);
当 provider 可以提交 rpc 请求时。
ethereum.on('disconnect', handler: (error: ProviderRpcError) => void);
节点无法提交 rpc 请求时。此时,前端也无法接受节点的请求,需要重新建立连接后,重载页面。
ethereum.on('accountsChanged', handler: (accounts: Array<string>) => void);
当连接的用户改变时,这里的 accounts
是只有一个元素的数组或者是空数组,数组的元素是字符串形式的地址。
ethereum.on('chainChanged', handler: (chainId: string) => void);
连接的链改变时,应该重载页面
ethereum.on('chainChanged', (_chainId) => window.location.reload());
当收到特定消息时触发
- interface ProviderMessage {
- type: string;
- data: unknown;
- }
-
- ethereum.on('message', handler: (message: ProviderMessage) => void);
provider 接收的错误格式如下
- interface ProviderRpcError extends Error {
- message: string;
- code: number;
- data?: unknown;
- }
其中,code
属性表示错误类型:
Code | Message | Meaning | Category |
---|
Status code | Name | Description |
---|
ethereum._metamask.isUnlocked()
返回一个 布尔的 promise , 钱包是否已经解锁。这是一个实验性的API,非标准API
ethereum.request(args)
提供了封装 Json-RPC 的用法。但是注意,这个用法适用于以太坊所有客户端,但是不一定适用于所有钱包。请求失败会报错,应该保证错误处理。
主要用法如下:( the Ethereum wiki 和 metamask API 有所有用法)
网站需要有相对应的许可才可以调用钱包的某些方法,目前唯一需要许可的方法是 eth_accounts
,访问用户账户。在 EIP-2255 对许可系统做了一次很好的讨论。
eth_requestAccounts
在 EIP-1102 提出了使用这个方法 调用 eth_accounts
的方法获取地址,返回 promise, 只有一个十六进制地址的元素的字符数组,如果用户拒绝,返回 4001
错误。
- function connect() {
- ethereum
- .request({ method: 'eth_requestAccounts' })
- .then(handleAccountsChanged)
- .catch((err) => {
- if (err.code === 4001) {
- // 用户拒绝连接
- console.log('Please connect to MetaMask.');
- } else {
- console.error(err);
- }
- });
- }
eth_decrypt
参数
Array
string
- 使用公钥生成的公开密钥加密的密文。string
- 公钥对应的地址。返回值
string
- 明文。
- ethereum
- .request({
- method: 'eth_decrypt',
- params: [encryptedMessage, accounts[0]],
- })
- .then((decryptedMessage) =>
- console.log('The decrypted message is:', decryptedMessage)
- )
- .catch((error) => console.log(error.message));
eth_getEncryptionPublicKey
参数
Array
string
- 公钥的来源地址返回值
string
- 以太坊账户的公开的加密后的密钥。
这个加密使用的是 ref="https://github.com/dchest/tweetnacl-js">nacl 里面的
X25519_XSalsa20_Poly1305
算法。
- let encryptionPublicKey;
-
- ethereum
- .request({
- method: 'eth_getEncryptionPublicKey',
- params: [accounts[0]], // you must have access to the specified account
- })
- .then((result) => {
- encryptionPublicKey = result;
- })
- .catch((error) => {
- if (error.code === 4001) {
- // EIP-1193 userRejectedRequest error
- console.log("We can't encrypt anything without the key.");
- } else {
- console.error(error);
- }
- });
加密需要选择一些密码学的库,然后使用上面提到的算法。
wallet_addEthereumChain
添加了新的链的元数据后,用户就可以切换到不同的链。链的元数据的格式非常严格,任何错误都会报错,并且需要:
eth_chainId
)。参数
Array
AddEthereumChainParameter
- 链的元数据rpcUrls
和 blockExplorerUrls
数组必须要有一个元素,且只是用第一个元素。
- interface AddEthereumChainParameter {
- chainId: string; // A 0x-prefixed hexadecimal string
- chainName: string;
- nativeCurrency: {
- name: string;
- symbol: string; // 2-6 characters long
- decimals: 18;
- };
- rpcUrls: string[];
- blockExplorerUrls?: string[];
- iconUrls?: string[]; // Currently ignored.
- }
返回值
null
- 成功为 null,失败则是错误类型。
wallet_switchEthereumChain
正确添加链信息后,可以这样切换链。
- try {
- await ethereum.request({
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0xf00' }],
- });
- } catch (switchError) {
- // 表示钱包未能连接到这条链
- await ethereum.request({
- method: 'wallet_addEthereumChain',
- params: [{ chainId: '0xf00', rpcUrl: 'https://...' /* ... */ }],
- });
- } catch (addError) {
- // handle "add" error
- }
- }
- // handle other "switch" errors
- }
参数
Array
SwitchEthereumChainParameter
- 将要连接到的链的元数据,一般只要链 Id 即可.- interface AddEthereumChainParameter {
- chainId: string; // A 0x-prefixed hexadecimal string
- }
到这里应该已经对大部分API的用法有所了解,更多的可以查阅文档。
钱包保存着密钥,可以用授权网站,给数据签名。在 metamask 中有 5 种现行的签名方式,
eth_sign
personal_sign
signTypedData
(currently identical to signTypedData_v1
)signTypedData_v1
signTypedData_v3
signTypedData_v4
eth_sign
可以对任何哈希签名,也可以对任何数据和交易签名,但是也因此容易被钓鱼网站攻击。
personal_sign
只能添加固定的数据,因此安全性较高,而且可以使用 UTF-8 编码变得更具可读性。用法见 Metamask 文档 。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。