3 各钱包插件前端交互

文档描述目前主流的网页端以及移动端与链上热钱包的交互方式与规则

网页端钱包交互

  1. 热钱包的连接与调用

  2. 热钱包与智能合约的交互

热钱包的连接与调用

通过浏览器扩展程序的联动, 在前端环境中获取用户的钱包信息并取得用户的授权。文章以主流的钱包扩展程序Metamask为例子进行说明。前端代码采用ReactJS进行示例. 参考文档: https://metamask.io/

MetaMask 是一个开源的以太坊钱包,能帮助用户方便地管理自己的以太坊数字资产.

MetaMask 将一个全局 API 注入到其用户访问的网站中window.ethereum。该 API 允许网站请求用户的以太坊账户,从用户连接的区块链中读取数据,并建议用户签署消息和交易。提供者对象的存在表示以太坊用户。

window.ethereum - 由metamask全局注入的API,可以用来访问链上环境的RPC-endpoint

  • 发起交易

  • 申请钱包授权

    ethereum.request({ja
            method: 'eth_requestAccounts',
          });
  • 获取当前链ID

    const chainId = await ethereum.request({ method: 'eth_chainId' });

  • 账户变更监听

ethereum.on('accountsChanged', handleAccountsChanged);

// For now, 'eth_accounts' will continue to always return an array
function handleAccountsChanged(accounts) {
  if (accounts.length === 0) {
    // MetaMask is locked or the user has not connected any accounts
    console.log('Please connect to MetaMask.');
  } else if (accounts[0] !== currentAccount) {
    currentAccount = accounts[0];
    // Do any other work!
  }
}

/*********************************************/
/* Access the user's accounts (per EIP-1102) */
/*********************************************/

// You should only attempt to request the user's accounts in response to user
// interaction, such as a button click.
// Otherwise, you popup-spam the user like it's 1999.
// If you fail to retrieve the user's account(s), you should encourage the user
// to initiate the attempt.
document.getElementById('connectButton', connect);

// 
  • 链变更监听

    ethereum.on('chainChanged', handleChainChanged);
    
    function handleChainChanged(_chainId) {
      // We recommend reloading the page, unless you must do otherwise
      window.location.reload();
    }

//检查当前用户环境
//方式1:通过metmask库中的检查方法
import detectEthereumProvider from '@metamask/detect-provider';

const provider = await detectEthereumProvider();

if (provider) {
 
  startApp(provider); // initialize your app
} else {
  console.log('Please install MetaMask!');
}

// 方式2:通过window的全局api进行检查
const provider_2 = window.ethereum
if(provider_2){
 startApp(provider); // initialize your app
} else {
  console.log('Please install MetaMask!');
}

完整代码示例. dapp初始化部分

import { useState, useEffect } from 'react';

function App() {
  const [haveMetamask, sethaveMetamask] = useState(true);

  useEffect(() => {
    const { ethereum } = window;
    const checkMetamaskAvailability = async () => {
      if (!ethereum) {
        sethaveMetamask(false);
      }
      sethaveMetamask(true);
    };
    checkMetamaskAvailability();
  }, []);

  return (
  // ...
  );
}

export default App;
const [isConnected, setIsConnected] = useState(false);
const [accountAddress, setAccountAddress] = useState('');
const [currentChain, setCurrentChain] = useState('')
// 链接钱包的handler
const connectWallet = async () => {
const { ethereum } = window;
  if (!ethereum) {
    sethaveMetamask(false);
  }

  const accounts = await ethereum.request({
    method: 'eth_requestAccounts',
  });
   setAccountAddress(accounts[0]);
};
// 获取用户钱包中的eth的余额
// 该余额与当前链的主要流通货币相关
// 比如币安链 - 则获取的是BNB的余额
import web3 from 'web3';
const provider = new web3(window.ethereum);

const connectWallet = async () => {
  try {
    //...

    let balance = await provider.getBalance(accounts[0]);
    let bal = web3.utils.formatEther(balance);

    setAccountBalance(bal);

  } catch (error) {
    setIsConnected(false);
  }
};
// 获取当前链ID
const getChainId = async() => {
 try {
    //...

    const chainId = await ethereum.request({ method: 'eth_chainId' });
    setChainId(chainId);
  } catch (error) {
    setChainId(null);
  }
}
// 当钱包地址和链Id都获取完毕后, 需要监听他们的变化以便于用户变更后能及时给到回馈
  const handleAccountChange = (accounts) => {
    if (accounts[0] !== currentAddress) {
      setCurrentAddress(accounts[0])
    }
  }

  const handleChainChange = (chainId) => {
    if (chainId !== currentChainId) {
      window.location.reload();
    }
  }
  
  useEffect(() => {
    const { ethereum } = window;
    ethereum.on('accountsChanged', handleAccountChange);
    ethereum.on('chainChanged', handleChainChange)
  },[])

热钱包与链上合约交互

目前主流的用于与链上交互的js库有web3Js 和 etherJS,文章主要以web3进行讲解https://web3js.readthedocs.io/en/v1.2.11/#

合约对象的创建

abi https://docs.soliditylang.org/en/develop/abi-spec.html

合约应用二进制接口 (ABI) 是在以太坊生态系统中与合约交互的标准方式,既可以从区块链外部进行,也可以用于合约间交互。数据根据其类型进行编码,如本规范中所述。编码不是自我描述的,因此需要一个模式才能解码。我们假设合约的接口函数是强类型的,在编译时已知并且是静态的。我们假设所有合约都将具有他们调用的任何合约的接口定义在编译时可用。本规范不涉及接口是动态的或仅在运行时才知道的合约。可以将其理解为一个合约的interface。包含了所有的方法名.

address

当智能合约成功部署到链上环境之后会自动生成一个地址作为该合约的标识符以便于进行查找以及定位

const TestContract = new web3.eth.Contract(TestAbi, TestAddress);
// TestAbi - abi json数据
// TestAddress - 智能合约在链上的地址

合约方法的调用

call - 用于读取当前只能合约状态的方法, 可以理解为只读方法, 过程中不需要消耗任何gas费用

send - 用于改变合约状态值所需的方法, 本质上是发起一笔交易从而改变合约中的状态, gas费用根据方法的复杂程度以及以太坊环境的平均gas高低不一。 交易方法的调用需要进行钱包的授权, 并从授权的钱包中扣取相应的gas费用

// 假设当前合约对象为TestContract
// 该合约包含一个制度方法viewStatus来查看当前合约活跃状态的只读方法
// 以及一个changeStatus来更改当前合约状态的交易方法

// 只读方法的调用
const res = await TestContract.methods.viewStatus().call()
console.log(res) // 0

// 交易方法的调用
// 方法传递的参数由合约开发者制定. 需要注意的是, 传参的格式会直接影响交易的成功, 进行合约方法对接需要与后端同步并确认所有的参数形式确保没有错误

const res = await TestContract.methods.changeStatus(2).send({ from: address })
// 若交易成功 可以通过打印res.blockHash 获取成功后的交易哈希, 在区块链浏览器上查看成功后的交易细节

Last updated