近一两年随着DeFi的火热,DApp开发也受到越来越多的关注,单纯从概念来说,DApp还是比较容易理解的,也就是App + decentralization(去中心化)。而去中心化我个人认为主要体现在两个方面,一是账户体系,二是服务或数据。
账户体系也就是我们的钱包账户。
服务或数据,则对应我们的智能合约以及链上数据,获取这些数据非常简单,不需要Token认证,也不需要session,cookie,一切都是可以公开访问的,甚至你可以直接在一些区块链浏览器直接操作合约。
本文仅介绍以太坊系列的DApp开发,其他链原理差不太多。
目标
- 了解一些基础的区块链概念和操作
- 熟悉与MetaMask钱包的交互
- 了解ERC20 Token、并与ERC20合约进行交互
- 在Mdex的某个矿池中抵押LP Token(流动性证明),赎回LP Token
环境与工具
- 需要安装MetaMask钱包并创建或导入账号
- ethers.js (与以太坊区块链及其生态系统进行交互的JavaScript库),也可以用web3.js
- create-react-app(demo相关)
- Heco区块链浏览器
钱包操作
MetaMask安装完成并运行后,可以在Chrome控制台打印MetaMask注入的window.ethereum对象
关于ethereum对象,我们只需要关心ethereum.request
就足够了,MetaMask 使用ethereum.request(args)
方法来包装 RPC API。这些 API 基于所有以太坊客户端公开的接口。 简单来说钱包交互的大部分操作都是由request()
方法实现,通过传入不同的方法名来区分。
⚠️即使ethereum对象中提供了chainId,isMetaMask,selectAddress属性,我们也不能完全相信这些属性,他们是不稳定或不标准,不建议使用。我们可以通过上面说的request方法,拿到可靠的数据。
钱包通过method方法名,进行对应的实现 以获取钱包地址为例
调用ethereum.request({ method: "eth_requestAccounts" })
,钱包实现了该方法,那么就可以拿到钱包的地址了。
case "eth_requestAccounts":
return ["0xFD513D86a57BAFeAf46ddcC1bDDf629409A03cCf"]
常见的钱包操作
-
连接钱包
const accounts = await ethereum.request({ method: "eth_requestAccounts" }); const account = accounts[0];
-
监听网络和账号切换事件
//监听账户变化 ethereum.on("accountsChanged", (accounts) => { setAccount(accounts[0]); }); //监听网络变化 ethereum.on("chainChanged", (chainId) => { setChainId(chainId); });
-
添加自定义代币(添加自己的平台币到钱包账户,是个非常方便的功能。基于EIP-747)
const Dog = { symbol: "Dog", decimals: 18, image: "https://hecoinfo.com/token/images/dogtoken_32.png", address: "0xb3863e02d6930762933f672ca134c1ccecd0d413", }; ethereum.request({ method: "wallet_watchAsset", params: { type: "ERC20", // Initially only supports ERC20, but eventually more! options: Dog, }, });
-
添加自定义网络(像Mdex支持同时支持ETH、HECO、BSC,MetaMask只有ETH主网和测试网,可以直接通过此协议增加对应的网络到钱包,如果钱包支持的话,基于EIP-3085,仅支持非内置网络),社区目前有个EIP-3326提案,可以直接在以太坊网络进行切换。
const Dog = { symbol: "Dog", decimals: 18, image: "https://hecoinfo.com/token/images/dogtoken_32.png", address: "0xb3863e02d6930762933f672ca134c1ccecd0d413", }; ethereum.request({ method: "wallet_watchAsset", params: { type: "ERC20", // Initially only supports ERC20, but eventually more! options: Dog, }, });
Provider
Provider 是一个连接以太坊网络的抽象,用与查询以太坊网络状态或者发送更改状态的交易。
MetaMask注入的window.ethereum
就是一个Provider,一个RPC节点也是一个Provider,通过Provider,我们有了访问区块链的能力。 在连接到钱包的情况下,通常使用钱包的Provider就可以了,ethers.providers.Web3Provider(ethereum)
如果只需要查询一些区块链数据,可以使用EtherscanProvider 和 InfuraProvider 连接公开的第三方节点服务提供商。JsonRpcProvider 和 IpcProvider 允许连接到我们控制或可以访问的以太坊节点。
获取当前账户余额
const balance = await provider.getBalance(account);
获取最新区块号
const blockNumber = await provider.getBlockNumber();
其他RPC操作,可以通过JSON-RPC查看。
ERC20 Token
ERC20 通证标准(ERC20 Token Standard)是通过以太坊创建通证时的一种规范。按照ERC20的规范可以编写一个智能合约,创建"可互换通证"。它并非强制要求,但遵循这个标准,所创建的通证可以与众多交易所,钱包等进行交互。目前的平台币基本都遵循ERC20标准。
contract ERC20 {
function name() constant returns (string name)
function symbol() constant returns (string symbol)
function decimals() constant returns (uint8 decimals)
function totalSupply() constant returns (uint totalSupply);
function balanceOf(address _owner) constant returns (uint balance);
function transfer(address _to, uint _value) returns (bool success);
function transferFrom(address _from, address _to, uint _value) returns (bool success);
function approve(address _spender, uint _value) returns (bool success);
function allowance(address _owner, address _spender) constant returns (uint remaining);
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
通过ethers.js可以连接ERC20的合约,合约编译后会生成ABI,合约部署后,会生成合约地址,开发者通过ABI和合约地址,对合约发送消息。
合约中的方法大致分为两种:视图方法(免费),非视图方法(消耗Gas),可以通过ABI查看方法类型。
-
初始化ERC20合约,如果是视图方法,只是进行数据的读取,传入Provider就可以,如果需要更改合约状态,需要用户签名,则需要传入Signer。
需要Token的合约地址和ERC20的合约ABI,可以通过浏览器查询到相关的信息,例如Dog
new ethers Contract( addressOrName, abi,providerOrSigner)
-
查询合约的相关信息,这里以Heco的Dog为例,其他的Token都是一样的。
const totalSupply = await contract.totalSupply(); const balance = await contract.balanceOf(account);
-
Transfer,转账的单位是Wei,可以通过etherjs提供的工具函数进行转换。
const contract = new ethers.Contract( Dog.address, ERC20ABI, provider.getSigner() ); const tx = await contract.transfer( "0xFD513D86a57BAFeAf46ddcC1bDDf629409A03cCf", parseEther("1.0") );
⚠️ERC20需要多加关注的是Approve()
方法以及transfer()
和transferFrom()
的区别,授权过的代币,被授权的那一方,可以通过调用transferFrom()
方法,转走你授权数量内的代币,所以授权是一个很危险的操作,假设你授权了一个不良的合约,那你会面临授权的token被转走的风险,即使你没有泄露私钥助记词。
抵押和赎回
挖矿合约仅需要了解大概过程,挖矿合约并非标准,涉及到具体业务 这里选择Mdex DEP-HUSD矿池进行操作,也就是抵押DEP-HUSD LP Token 获取Mdex 。 此处已经提前添加了LP Token以及进行了Approve操作。
操作成功与否可以通过Mdex页面进行查看或在浏览器进行查询
-
从HecoInfo上获取到Mdex Pool的ABI,通过ABI连接Mdex Pool的合约
const pool = new ethers.Contract( POOL_ADDRESS, POOLABI, provider.getSigner() );
-
抵押全部的LP
const pid = await pool.LpOfPid(LP_ADDRESS); const lpToken = new ethers.Contract(LP_ADDRESS, ERC20ABI, provider.getSigner()); //抵押 const lpBanlance = await lpToken.balanceOf(account); const tx = await pool.deposit(pid, lpBanlance);
-
赎回全部的LP
const pid = await pool.LpOfPid(LP_ADDRESS); const userInfo = await pool.userInfo(pid, account); const tx = await pool.withdraw(pid, userInfo[0]);
文档与三方库
便利三方库:web3-react use-wallet