当前位置:   article > 正文

DApp浏览器交互环境_tp钱包中的dapp浏览器怎么和钱包交互的

tp钱包中的dapp浏览器怎么和钱包交互的

浏览器环境

对 metamask 的介绍和应用场景,见官方文档,写的很好。其实这也是节点交互的一个很好的学习材料。

metamask 会提供一个可以通过window.ethereum 的全局API,可以:请求连接账户、获取用户连接的链的数据(如交易后的返回值和事件)、以及显示用户对交易的签名状态。 provider 的存在可以显示以太坊用户的对象。

检测环境

我们可以通过检查 window.ethereum 是否定义,来检查浏览器中是否运行运行 metamask

  1. if (typeof window.ethereum !== 'undefined') {
  2. console.log('MetaMask is installed!');
  3. }

如果要和其他兼容的钱包区分,则需要使用ethereum.isMetaMask ,返回布尔值

我们常用 ganache 来模拟区块链,实现快速的开发,尤其是可以 ganache-cli -m "助记词" 生成特定的用户和地址,很方便与 metamask 配合。

注意:由于交易的 nonce 机制,当我们重启了私链后,可能会出现冲突,导致交易阻塞,我们需要在设置-高级设置-重置账户。

注意:申请与 metamask 连接的按钮必须显示提供,不能自动申请连接,这是规范。

使用

对于任何DApp,都必须首先:

  • 检测 ethereum provider.
  • 检测 连接地网络。
  • 获取连接的账户。
  1. // 检测钱包
  2. import detectEthereumProvider from '@metamask/detect-provider';
  3. // 返回 provider
  4. const provider = await detectEthereumProvider();
  5. if (provider) {
  6. startApp(provider);
  7. } else {
  8. console.log('Please install MetaMask!');
  9. }
  10. function startApp(provider) {
  11. // 检测prover是否完整,未被更改
  12. if (provider !== window.ethereum) {
  13. console.error('Do you have multiple wallets installed?');
  14. }
  15. }
  16. //获取链Id
  17. const chainId = await ethereum.request({ method: 'eth_chainId' });
  18. handleChainChanged(chainId); //重新加载页面并且触发事件
  19. ethereum.on('chainChanged', handleChainChanged);
  20. function handleChainChanged(_chainId) {
  21. // 建议刷新页面,因为连接的节点不同了。
  22. window.location.reload();
  23. }
  24. //改变连接的用户
  25. let currentAccount = null;
  26. ethereum
  27. .request({ method: 'eth_accounts' }) //返回的是一个数组
  28. .then(handleAccountsChanged)
  29. .catch((err) => {
  30. console.error(err);
  31. });
  32. ethereum.on('accountsChanged', handleAccountsChanged);
  33. function handleAccountsChanged(accounts) {
  34. if (accounts.length === 0) {
  35. // 未连接钱包或者钱包锁定了
  36. console.log('Please connect to MetaMask.');
  37. } else if (accounts[0] !== currentAccount) {
  38. currentAccount = accounts[0]; //切换账户
  39. }
  40. }
  41. //访问账户
  42. // 应该,点击按钮,再连接钱包。
  43. //
  44. // 如果获取用户失败,应该让用户重新点击按钮连接。
  45. document.getElementById('connectButton', connect);
  46. // 等待用户确定连接时,应该禁用申请访问账户的按钮,因为必须确认后才能进行下面操作
  47. function connect() {
  48. ethereum
  49. .request({ method: 'eth_requestAccounts' })
  50. .then(handleAccountsChanged)
  51. .catch((err) => {
  52. if (err.code === 4001) {
  53. // 用户拒绝连接
  54. console.log('Please connect to MetaMask.');
  55. } else {
  56. console.error(err);
  57. }
  58. });
  59. }

链ID编号:

0x11Ethereum Main Network (Mainnet)

ethereum.isMetaMask

检查是否存在 metamask 提供的provider.

ethereum.isConnected() :

当前的 provider 是否连接到开放 json-rpc 的节点(能否发送请求),但是这在切换网络时可能有延迟。

ethereum.request(args)

这是发起请求的主要办法,它返回的是 promise

  1. interface RequestArguments {
  2. method: string;
  3. params?: unknown[] | object;
  4. }
  5. ethereum.request(args: RequestArguments): Promise<unknown>;

参数和返回值会根据方法的改变而改变。如果请求失败会返回 error 的对象

例子:

  1. params: [
  2. {
  3. from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
  4. to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
  5. gas: '0x76c0', // 30400
  6. gasPrice: '0x9184e72a000', // 10000000000000
  7. value: '0x9184e72a', // 2441406250
  8. data:
  9. '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
  10. },
  11. ];
  12. ethereum
  13. .request({
  14. method: 'eth_sendTransaction',
  15. params,
  16. })
  17. .then((result) => {
  18. // The result varies by by RPC method.
  19. // For example, this method will return a transaction hash hexadecimal string on success.
  20. })
  21. .catch((error) => {
  22. // If the request fails, the Promise will reject with an error.
  23. });

事件

provider 提供了 Node.js 的 EventEmitter,下面是常见的事件。

connect

  1. interface ConnectInfo {
  2. chainId: string;
  3. }
  4. ethereum.on('connect', handler: (connectInfo: ConnectInfo) => void);

当 provider 可以提交 rpc 请求时。

disconnect

ethereum.on('disconnect', handler: (error: ProviderRpcError) => void);

节点无法提交 rpc 请求时。此时,前端也无法接受节点的请求,需要重新建立连接后,重载页面。

accountsChanged

ethereum.on('accountsChanged', handler: (accounts: Array<string>) => void);

当连接的用户改变时,这里的 accounts 是只有一个元素的数组或者是空数组,数组的元素是字符串形式的地址。

chainChanged

ethereum.on('chainChanged', handler: (chainId: string) => void);

连接的链改变时,应该重载页面

ethereum.on('chainChanged', (_chainId) => window.location.reload());

message

当收到特定消息时触发

  1. interface ProviderMessage {
  2. type: string;
  3. data: unknown;
  4. }
  5. ethereum.on('message', handler: (message: ProviderMessage) => void);

错误

provider 接收的错误格式如下

  1. interface ProviderRpcError extends Error {
  2. message: string;
  3. code: number;
  4. data?: unknown;
  5. }

其中,code 属性表示错误类型:

  • 4001:用户拒绝发送这次请求。
  • -32602:参数无效
  • -32603:内部错误。

具体见:EIP-1193 and EIP-1474 .

CodeMessageMeaningCategory
Status codeNameDescription

实验性API: ethereum._metamask.isUnlocked()

返回一个 布尔的 promise , 钱包是否已经解锁。这是一个实验性的API,非标准API

  1. RPC API

ethereum.request(args)提供了封装 Json-RPC 的用法。但是注意,这个用法适用于以太坊所有客户端,但是不一定适用于所有钱包。请求失败会报错,应该保证错误处理。

主要用法如下:( the Ethereum wiki 和 metamask API 有所有用法)

  1. eth_accounts(opens new window)
  2. "https://eth.wiki/json-rpc/API#eth_call">eth_call(opens new window)
  3. eth_getBalance(opens new window)
  4. eth_sendTransaction(opens new window)
  5. "https://eth.wiki/json-rpc/API#eth_sign">eth_sign(opens new window) 

请求钱包许可

网站需要有相对应的许可才可以调用钱包的某些方法,目前唯一需要许可的方法是 eth_accounts ,访问用户账户。在 EIP-2255 对许可系统做了一次很好的讨论。

eth_requestAccounts

在 EIP-1102 提出了使用这个方法 调用 eth_accounts 的方法获取地址,返回 promise, 只有一个十六进制地址的元素的字符数组,如果用户拒绝,返回 4001 错误。

  1. function connect() {
  2. ethereum
  3. .request({ method: 'eth_requestAccounts' })
  4. .then(handleAccountsChanged)
  5. .catch((err) => {
  6. if (err.code === 4001) {
  7. // 用户拒绝连接
  8. console.log('Please connect to MetaMask.');
  9. } else {
  10. console.error(err);
  11. }
  12. });
  13. }

eth_decrypt

参数

  • Array
  1. string - 使用公钥生成的公开密钥加密的密文。
  2. string - 公钥对应的地址。

返回值

string - 明文。

  1. ethereum
  2. .request({
  3. method: 'eth_decrypt',
  4. params: [encryptedMessage, accounts[0]],
  5. })
  6. .then((decryptedMessage) =>
  7. console.log('The decrypted message is:', decryptedMessage)
  8. )
  9. .catch((error) => console.log(error.message));

eth_getEncryptionPublicKey

参数

  • Array
  1. string - 公钥的来源地址

返回值

string - 以太坊账户的公开的加密后的密钥。

这个加密使用的是 ref="https://github.com/dchest/tweetnacl-js">nacl 里面的 X25519_XSalsa20_Poly1305 算法。

  1. let encryptionPublicKey;
  2. ethereum
  3. .request({
  4. method: 'eth_getEncryptionPublicKey',
  5. params: [accounts[0]], // you must have access to the specified account
  6. })
  7. .then((result) => {
  8. encryptionPublicKey = result;
  9. })
  10. .catch((error) => {
  11. if (error.code === 4001) {
  12. // EIP-1193 userRejectedRequest error
  13. console.log("We can't encrypt anything without the key.");
  14. } else {
  15. console.error(error);
  16. }
  17. });

加密需要选择一些密码学的库,然后使用上面提到的算法。

wallet_addEthereumChain

添加了新的链的元数据后,用户就可以切换到不同的链。链的元数据的格式非常严格,任何错误都会报错,并且需要:

  • RPC 接收端信息需要和调用请求一致。
  • RPC 返回的链 Id 需要一致(如 eth_chainId)。
  • 链 Id 不能够和钱包自带的默认 Id 一样。

参数

  • Array
  1. AddEthereumChainParameter - 链的元数据

rpcUrls 和 blockExplorerUrls 数组必须要有一个元素,且只是用第一个元素。

  1. interface AddEthereumChainParameter {
  2. chainId: string; // A 0x-prefixed hexadecimal string
  3. chainName: string;
  4. nativeCurrency: {
  5. name: string;
  6. symbol: string; // 2-6 characters long
  7. decimals: 18;
  8. };
  9. rpcUrls: string[];
  10. blockExplorerUrls?: string[];
  11. iconUrls?: string[]; // Currently ignored.
  12. }

返回值

null - 成功为 null,失败则是错误类型。

wallet_switchEthereumChain

正确添加链信息后,可以这样切换链。

  1. try {
  2. await ethereum.request({
  3. method: 'wallet_switchEthereumChain',
  4. params: [{ chainId: '0xf00' }],
  5. });
  6. } catch (switchError) {
  7. // 表示钱包未能连接到这条链
  8. await ethereum.request({
  9. method: 'wallet_addEthereumChain',
  10. params: [{ chainId: '0xf00', rpcUrl: 'https://...' /* ... */ }],
  11. });
  12. } catch (addError) {
  13. // handle "add" error
  14. }
  15. }
  16. // handle other "switch" errors
  17. }

参数

  • Array
  1. SwitchEthereumChainParameter - 将要连接到的链的元数据,一般只要链 Id 即可.
  1. interface AddEthereumChainParameter {
  2. chainId: string; // A 0x-prefixed hexadecimal string
  3. }

到这里应该已经对大部分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 文档 。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/861564
推荐阅读
相关标签
  

闽ICP备14008679号