EIP-712 使用详解
之前的文章我们介绍过如何对数据进行签名,利用签名技术我们可以实现一些功能例如白名单校验等。但是这种签名技术的应用场景比较简单,一般就是给一串字符串,或者一串哈希签名,如果我们想为更复杂的数据签名就无法实现了。 EIP-712 的出现就是为了解决这个问题,利用 EIP-712,我们可以对更大的数据集,例如对结构体进行签名。那么这种签名格式有什么实际的应用场景呢。使用过 Uniswap,PancakeSwap 等 DEX 的朋友应该有印象,在移除 LP 流动性的时候,我们需要先签名,然后再发送一笔交易移除流动性。正常情况下,其实应该我们先调用 LP 代币的授权方法,授权 DEX 合约可以转移我们的 LP,然后再去移除流动性。而这种二合一的实现正是应用了 EIP-712。它帮助我们仅仅签名一次,就可以将两步交易合并为一步交易,从而节省 Gas 费用。这篇文章我们就来看看 EIP-712 到底是怎么使用的。基本结构EIP712Domain顾名思义,是一个与域相关的结构体,总共包含五个字段:name,合约或者协议的名称version,合约的版本chainId,合约部署的链 Id,一般使用 ...
流动性挖矿-合约原理详解
流动性挖矿应该是上个牛市最火热的内容,基本上整个 DeFi 都是在围绕着流动性挖矿展开的,今天我们就来看看它到底是什么以及合约代码层面是怎么实现的。流动性挖矿简介首先我们先从用户的角度来理解一下流动性挖矿是什么,实际上就是用户通过在合约中质押一个 token 从而赚取另一个 token 的过程。例如,SushiSwap 最初推出的 DEX 流动性挖矿,用户可以通过将 SushiSwap 的 LP token 质押到合约中赚取 Sushi token。那么这个奖励具体是怎么发放以及如何实现的呢?我们今天就来研究一下这部分内容。 先来看几个例子: 一:假设有一个流动性挖矿的合约,可以质押 A token 赚取 B token。它在 0 秒时开始活动,每秒奖励 R 个 B token。此时有用户 Alice 在第 3 秒时质押了 2 个 A token,并且之后没有其他人参与,在第 8 秒时取出 token,图示:那么他在此时获得的收益就是:5R = (2 / 2) * (8 - 3) * R 其中,第一个 2 是用户 A 质押的数量,第二个 2 是合约中质押的总量,(8-3)是用户 ...
CREATE2 操作码使用方法详解
CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相...
Smart Contract Developer
EIP-712 使用详解
之前的文章我们介绍过如何对数据进行签名,利用签名技术我们可以实现一些功能例如白名单校验等。但是这种签名技术的应用场景比较简单,一般就是给一串字符串,或者一串哈希签名,如果我们想为更复杂的数据签名就无法实现了。 EIP-712 的出现就是为了解决这个问题,利用 EIP-712,我们可以对更大的数据集,例如对结构体进行签名。那么这种签名格式有什么实际的应用场景呢。使用过 Uniswap,PancakeSwap 等 DEX 的朋友应该有印象,在移除 LP 流动性的时候,我们需要先签名,然后再发送一笔交易移除流动性。正常情况下,其实应该我们先调用 LP 代币的授权方法,授权 DEX 合约可以转移我们的 LP,然后再去移除流动性。而这种二合一的实现正是应用了 EIP-712。它帮助我们仅仅签名一次,就可以将两步交易合并为一步交易,从而节省 Gas 费用。这篇文章我们就来看看 EIP-712 到底是怎么使用的。基本结构EIP712Domain顾名思义,是一个与域相关的结构体,总共包含五个字段:name,合约或者协议的名称version,合约的版本chainId,合约部署的链 Id,一般使用 ...
流动性挖矿-合约原理详解
流动性挖矿应该是上个牛市最火热的内容,基本上整个 DeFi 都是在围绕着流动性挖矿展开的,今天我们就来看看它到底是什么以及合约代码层面是怎么实现的。流动性挖矿简介首先我们先从用户的角度来理解一下流动性挖矿是什么,实际上就是用户通过在合约中质押一个 token 从而赚取另一个 token 的过程。例如,SushiSwap 最初推出的 DEX 流动性挖矿,用户可以通过将 SushiSwap 的 LP token 质押到合约中赚取 Sushi token。那么这个奖励具体是怎么发放以及如何实现的呢?我们今天就来研究一下这部分内容。 先来看几个例子: 一:假设有一个流动性挖矿的合约,可以质押 A token 赚取 B token。它在 0 秒时开始活动,每秒奖励 R 个 B token。此时有用户 Alice 在第 3 秒时质押了 2 个 A token,并且之后没有其他人参与,在第 8 秒时取出 token,图示:那么他在此时获得的收益就是:5R = (2 / 2) * (8 - 3) * R 其中,第一个 2 是用户 A 质押的数量,第二个 2 是合约中质押的总量,(8-3)是用户 ...
CREATE2 操作码使用方法详解
CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相...
Smart Contract Developer

Subscribe to xyyme.eth

Subscribe to xyyme.eth
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
Ethers.js 是一个与 evm-compitable 区块链交互的 JavaScript 库。用户通过编写 JavaScript 代码就可以与区块链进行交互,包括读写等操作。同时还有一个名为 web3.js 的库也可以与区块链交互,与之相比,Ethers.js 更加轻量级,上手更加简单。
npm install --save ethers
// 需要导入 ethers 包
const { ethers } = require("ethers");
// 构建 provider,可以理解为与区块链交互的桥梁
// 1. 可使用最基础的 JsonRpcProvider 进行构建 provider
const INFURA_ID = 'xxx..';
const provider = new ethers.providers.JsonRpcProvider(`https://mainnet.infura.io/v3/${INFURA_ID}`);
// 2. 也可使用封装好的特定 provider,例如 AlchemyProvider,InfuraProvider
// 使用 alchemy 的时候,第一个参数若为 null 则代表主网
const ALCHEMY_ID = `xxx...`;
const provider = new ethers.providers.AlchemyProvider('null', ALCHEMY_ID);
接下来我们看看一些常用的函数:
// 获取当前网络最新的区块号
let blockNumber = await provider.getBlockNumber();
// 获取余额,参数为地址,返回值是 bignumber 格式,单位为 wei
let balance = await provider.getBalance("0x...");
// 将 wei 格式转化为 ether 格式,bignumber 和 toString 格式都可以作为参数
let balance_in_ether = ethers.utils.formatEther(balance);
// 将 ether 转换为 wei 格式,返回值为 bignumber 格式
let result = ethers.utils.parseEther("1.0");
// 获取 gas price,返回值为 bignumber 格式
let gasPrice = await provider.getGasPrice();
// 获取内存 storage slot
// 第一个参数是合约地址,第二个参数是插槽位置
// 插槽位置可以使用十进制或者十六进制,十六进制需要加引号
await provider.getStorageAt("0x...", 3);
await provider.getStorageAt("0x...", "0x121");
// 获取地址的 nonce
await provider.getTransactionCount("0x...");
注意上面返回值为 bignumber 格式的数据,如果要在控制台打印,需要使用 toString() 进行转换。同时,由于 JavaScript 中的语法限制,所有 await 的语句都要放在 async 函数中。也就是说,要执行带有 await 的语句,必须为:
const main = async () => {
let gasPrice = await provider.getGasPrice();
}
main();
// 构建钱包有两种方法
// 1. 直接通过 provider 和 私钥 构建
const privateKey1 = `0x...` // Private key of account 1
const wallet = new ethers.Wallet(privateKey1, provider);
// 2. 先通过 私钥 构建钱包,然后连接 provider
wallet = new ethers.Wallet(privateKey1);
walletSigner = wallet.connect(provider);
// 获取钱包地址
let address = await wallet.getAddress();
// eth 转账
const tx = await wallet.sendTransaction({
to: wallet2.getAddress(),
value: ethers.utils.parseEther("100")
});
// 等待交易上链
await tx.wait();
// 打印交易信息
console.log(tx);
// 可以直接通过 函数签名 构建 abi
// 需要用到哪个函数就写哪个,不需要写出全部的函数签名
const ERC20_ABI = [
"function name() public view returns (string)",
"function symbol() view returns (string)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address) view returns (uint)",
"event Transfer(address indexed from, address indexed to, uint amount)"
];
// 合约地址
const address = '0x...';
// 通过 地址,abi,provider 构建合约对象
const contract = new ethers.Contract(address, ERC20_ABI, provider);
// 读取 name() 的值
const name = await contract.name();
console.log(name.toString());
// 读取 symbol() 的值
const symbol = await contract.symbol();
console.log(symbol);
// 读取 ERC20 合约中 balanceOf 的值
const balance = await contract.balanceOf('0x....');
console.log(balance.toString());
// 获取钱包的 ERC20 余额
const balance = await contract.balanceOf(wallet.getAddress());
// 合约连接钱包对象
const contractWithWallet = contract.connect(wallet);
// 调用合约的 transfer 方法向其他账户转账
// 注意这里是调用 ERC20 合约的 transfer 函数,而不是原生货币转账
// 如果要调用 approve 函数,则为 contractWithWallet.approve
const tx = await contractWithWallet.transfer('0x...', balance);
// 等待交易上链
await tx.wait();
console.log(tx);
// Transfer 事件要在 abi 中声明
// 括号中的参数分别对应 Transfer 事件的参数
contract.on("Transfer", (from, to, amount, event) => {
console.log(`${from} sent ${ethers.utils.formatEther(amount)} to ${to}`);
});
// 这里是指定参数的监听行为
// 例如,我只想监听接收人是 `0x1234....` 的事件,那么就这样指定地址
// 构造一个 filter,然后通过 filter 筛选
filter = contract.filters.Transfer(null, '0x1234....');
// Receive an event when that filter occurs
contract.on(filter, (from, to, amount, event) => {
// The to will always be "address"
console.log(`I got ${ethers.utils.formatEther(amount)} from ${from}.`);
});
// 创建 指定发送人是 myAddress 的 filter
const myAddress = `0x...`;
filterFrom = contract.filters.Transfer(myAddress, null);
// 创建 指定接收人是 myAddress 的 filter
filterTo = contract.filters.Transfer(null, myAddress);
// 扫描指定区块的范围
console.log(await contract.queryFilter(filterFrom, 14692250, 14693250))
// 扫描最近 1000 个区块
console.log(await contract.queryFilter(filterFrom, -1000))
// 扫描所有区块
await daiContract.queryFilter(filterTo)
// 签名 utf-8
const signature = await wallet.signMessage('hello world');
console.log(signature);
// 更常见的 case 是签名 hash,长度为 32 字节
// 注意签名十六进制时,必须要将其转换为数组格式
// This string is 66 characters long
message = "0x4c8f18581c0167eb90a761b4a304e009b924f03b619a0c0e8ea3adfce20aee64";
// This array representation is 32 bytes long
messageBytes = ethers.utils.arrayify(message);
// To sign a hash, you most often want to sign the bytes
const signature2 = await wallet.signMessage(messageBytes);
console.log(signature2);
// 我们上面在做一些写操作,例如转 ETH,写合约等操作时
// 使用的 gasLimit、gasPrice,都是由链上获取的默认数值
// 如果想要手动指定,需要添加一个额外的参数
// 声明一个对象,填写需要覆盖的字段,例如
let overrides = {
gasLimit: 230000,
maxFeePerGas: ethers.utils.parseUnits('12', 'gwei'),
maxPriorityFeePerGas: ethers.utils.parseUnits('3', 'gwei'),
nonce: (await provider.getTransactionCount(wallet.getAddress()))
}
// 将 overrides 作为最后一个参数
await walletWithSigner.transfer(wallet2.getAddress(), amount, overrides)
本文列出了一些常用的用法,作为基本的使用基本够用了。查询更多方法可以查询 Ethers.js 的官方文档。总的来说,Ethers.js 还是比较简单的,上手也比较快。
欢迎和我交流
Ethers.js 是一个与 evm-compitable 区块链交互的 JavaScript 库。用户通过编写 JavaScript 代码就可以与区块链进行交互,包括读写等操作。同时还有一个名为 web3.js 的库也可以与区块链交互,与之相比,Ethers.js 更加轻量级,上手更加简单。
npm install --save ethers
// 需要导入 ethers 包
const { ethers } = require("ethers");
// 构建 provider,可以理解为与区块链交互的桥梁
// 1. 可使用最基础的 JsonRpcProvider 进行构建 provider
const INFURA_ID = 'xxx..';
const provider = new ethers.providers.JsonRpcProvider(`https://mainnet.infura.io/v3/${INFURA_ID}`);
// 2. 也可使用封装好的特定 provider,例如 AlchemyProvider,InfuraProvider
// 使用 alchemy 的时候,第一个参数若为 null 则代表主网
const ALCHEMY_ID = `xxx...`;
const provider = new ethers.providers.AlchemyProvider('null', ALCHEMY_ID);
接下来我们看看一些常用的函数:
// 获取当前网络最新的区块号
let blockNumber = await provider.getBlockNumber();
// 获取余额,参数为地址,返回值是 bignumber 格式,单位为 wei
let balance = await provider.getBalance("0x...");
// 将 wei 格式转化为 ether 格式,bignumber 和 toString 格式都可以作为参数
let balance_in_ether = ethers.utils.formatEther(balance);
// 将 ether 转换为 wei 格式,返回值为 bignumber 格式
let result = ethers.utils.parseEther("1.0");
// 获取 gas price,返回值为 bignumber 格式
let gasPrice = await provider.getGasPrice();
// 获取内存 storage slot
// 第一个参数是合约地址,第二个参数是插槽位置
// 插槽位置可以使用十进制或者十六进制,十六进制需要加引号
await provider.getStorageAt("0x...", 3);
await provider.getStorageAt("0x...", "0x121");
// 获取地址的 nonce
await provider.getTransactionCount("0x...");
注意上面返回值为 bignumber 格式的数据,如果要在控制台打印,需要使用 toString() 进行转换。同时,由于 JavaScript 中的语法限制,所有 await 的语句都要放在 async 函数中。也就是说,要执行带有 await 的语句,必须为:
const main = async () => {
let gasPrice = await provider.getGasPrice();
}
main();
// 构建钱包有两种方法
// 1. 直接通过 provider 和 私钥 构建
const privateKey1 = `0x...` // Private key of account 1
const wallet = new ethers.Wallet(privateKey1, provider);
// 2. 先通过 私钥 构建钱包,然后连接 provider
wallet = new ethers.Wallet(privateKey1);
walletSigner = wallet.connect(provider);
// 获取钱包地址
let address = await wallet.getAddress();
// eth 转账
const tx = await wallet.sendTransaction({
to: wallet2.getAddress(),
value: ethers.utils.parseEther("100")
});
// 等待交易上链
await tx.wait();
// 打印交易信息
console.log(tx);
// 可以直接通过 函数签名 构建 abi
// 需要用到哪个函数就写哪个,不需要写出全部的函数签名
const ERC20_ABI = [
"function name() public view returns (string)",
"function symbol() view returns (string)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address) view returns (uint)",
"event Transfer(address indexed from, address indexed to, uint amount)"
];
// 合约地址
const address = '0x...';
// 通过 地址,abi,provider 构建合约对象
const contract = new ethers.Contract(address, ERC20_ABI, provider);
// 读取 name() 的值
const name = await contract.name();
console.log(name.toString());
// 读取 symbol() 的值
const symbol = await contract.symbol();
console.log(symbol);
// 读取 ERC20 合约中 balanceOf 的值
const balance = await contract.balanceOf('0x....');
console.log(balance.toString());
// 获取钱包的 ERC20 余额
const balance = await contract.balanceOf(wallet.getAddress());
// 合约连接钱包对象
const contractWithWallet = contract.connect(wallet);
// 调用合约的 transfer 方法向其他账户转账
// 注意这里是调用 ERC20 合约的 transfer 函数,而不是原生货币转账
// 如果要调用 approve 函数,则为 contractWithWallet.approve
const tx = await contractWithWallet.transfer('0x...', balance);
// 等待交易上链
await tx.wait();
console.log(tx);
// Transfer 事件要在 abi 中声明
// 括号中的参数分别对应 Transfer 事件的参数
contract.on("Transfer", (from, to, amount, event) => {
console.log(`${from} sent ${ethers.utils.formatEther(amount)} to ${to}`);
});
// 这里是指定参数的监听行为
// 例如,我只想监听接收人是 `0x1234....` 的事件,那么就这样指定地址
// 构造一个 filter,然后通过 filter 筛选
filter = contract.filters.Transfer(null, '0x1234....');
// Receive an event when that filter occurs
contract.on(filter, (from, to, amount, event) => {
// The to will always be "address"
console.log(`I got ${ethers.utils.formatEther(amount)} from ${from}.`);
});
// 创建 指定发送人是 myAddress 的 filter
const myAddress = `0x...`;
filterFrom = contract.filters.Transfer(myAddress, null);
// 创建 指定接收人是 myAddress 的 filter
filterTo = contract.filters.Transfer(null, myAddress);
// 扫描指定区块的范围
console.log(await contract.queryFilter(filterFrom, 14692250, 14693250))
// 扫描最近 1000 个区块
console.log(await contract.queryFilter(filterFrom, -1000))
// 扫描所有区块
await daiContract.queryFilter(filterTo)
// 签名 utf-8
const signature = await wallet.signMessage('hello world');
console.log(signature);
// 更常见的 case 是签名 hash,长度为 32 字节
// 注意签名十六进制时,必须要将其转换为数组格式
// This string is 66 characters long
message = "0x4c8f18581c0167eb90a761b4a304e009b924f03b619a0c0e8ea3adfce20aee64";
// This array representation is 32 bytes long
messageBytes = ethers.utils.arrayify(message);
// To sign a hash, you most often want to sign the bytes
const signature2 = await wallet.signMessage(messageBytes);
console.log(signature2);
// 我们上面在做一些写操作,例如转 ETH,写合约等操作时
// 使用的 gasLimit、gasPrice,都是由链上获取的默认数值
// 如果想要手动指定,需要添加一个额外的参数
// 声明一个对象,填写需要覆盖的字段,例如
let overrides = {
gasLimit: 230000,
maxFeePerGas: ethers.utils.parseUnits('12', 'gwei'),
maxPriorityFeePerGas: ethers.utils.parseUnits('3', 'gwei'),
nonce: (await provider.getTransactionCount(wallet.getAddress()))
}
// 将 overrides 作为最后一个参数
await walletWithSigner.transfer(wallet2.getAddress(), amount, overrides)
本文列出了一些常用的用法,作为基本的使用基本够用了。查询更多方法可以查询 Ethers.js 的官方文档。总的来说,Ethers.js 还是比较简单的,上手也比较快。
欢迎和我交流
No activity yet