本文详细介绍了如何在以太坊主网上使用 Uniswap V2 进行代币交换,并提供了使用 Ethers.js 和 Uniswap SDK 的代码示例。文章涵盖了 Uniswap V2 的基本概念、开发环境设置、以及具体的代币交换实现步骤。
info
注意:本指南演示了如何在以太坊主网网络上进行 Uniswap V2 的代币交换。因此,请确保你在主网上有足够的 ETH 来进行交换。
Uniswap V2 并未正式部署在活跃的测试网上。因此,想要在测试网上测试交换和提供流动性的用户可能需要本地分叉链或使用非官方的 V2 合约(自行决定)。要在本地分叉链,你可以按照我们的指南操作:如何使用 Hardhat 分叉以太坊主网 或 如何使用 Foundry 分叉以太坊区块链。你需要确保使用正确的 RPC 网络 URL 和 Uniswap 合约地址,具体取决于你分叉的链。
并非所有用户在去中心化交易所交易代币时都通过前端 UI 进行交互。一些用户(或实体)通过智能合约或服务器端脚本以编程方式进行交易。本指南将演示如何使用 Javascript 和 Ethers.js 库在 Uniswap 上进行代币交换。我们的技术演示将包括代码讲解和在以太坊主网上从 ETH 交换到 DAI 的示例。
依赖项 | 版本 |
---|---|
node | 18.13.0 |
@uniswap/sdk | ^3.0.3 |
ethers | ^5.7.2 |
Uniswap 是领先的去中心化交易所(DEX)之一,它使用自动做市商(AMM)设计来促进交易。自 2018 年 V1 推出以来,Uniswap 不断推动 DeFi 的边界。最初的 V1 版本允许 ETH/ERC-20 对的交换,而 V2 进一步推动了边界,实现了 ERC-20/ERC-20 的交换。V3 提供了更高效的流动性,提供了集中的流动性和自定义的费用等级。最新的生产版本是 V4,它使用与 V3 类似的做市商设计,但将所有流动性保存在一个单一合约中(而不是为每个对创建一个单独的 LP 代币合约)。这种单一合约架构显著降低了 gas 费用,因为所有交换都在一个合约内进行。V4 还引入了“Hooks”,允许用户自定义流动性池、交换、费用和 LP 头寸之间的交互方式。
Uniswap 协议最初部署在以太坊上,但现在服务于许多链,如 Base、Optimism、Polygon、Arbitrum 等。
我们今天将介绍 Uniswap 的 V2 版本。虽然它不如 V4 最新,但它仍然具有相关性,因为许多 DeFi 协议使用相同的 AMM 架构;因此,我们今天构建的内容应该可以与其他克隆 Uniswap 代码库的 DeFi 项目(如 QuickSwap)一起操作。
Uniswap 将其 V2 合约分为两个存储库,Core 和 Periphery。Uniswap 还提供了一个有用的 SDK,用于开发和与其协议交互。核心合约涵盖了创建对(池)和跟踪余额,而 periphery 则帮助我们与这些核心合约进行交互。如果你想了解概述或学习如何获取代币的市场价格,请查看我们之前的指南 - 如何使用 Javascript 与 Uniswap 交互。
Uniswap Router
本节将更详细地解释我们与 Router 合约的交互。在成功调用任何交换方法之前,交易者必须首先批准路由器使用他们希望交换的 N 数量的代币。协议通常要求无限批准金额(以降低交易成本);然而,这并不是必需的,如果需要,你可以仅批准特定金额。approve() 函数如下所示:
function approve(address usr, uint wad) external returns (bool) {
allowance[msg.sender][usr] = wad;
emit Approval(msg.sender, usr, wad);
return true;
}
在 Uniswap 上交换代币包括使用 Router 智能合约中的几种交换方法之一。最常用的方法是 swapExactETHForTokens 和 swapExactTokensForTokens。这些方法各有不同的用例。
此函数用于当你想要将确切的 ETH 数量交换为尽可能多的输出代币,沿着路径确定的路线。请注意,此函数将你的 ETH 包装为 WETH。
function swapExactETHForTokens(
uint amountOutMin, // 必须接收到的最小输出代币数量,以便交易不会回滚
address[] calldata path, // 代币地址数组
address to, // 目标地址
uint deadline // Unix 时间戳,之后交易将回滚
) external payable returns (uint[] memory amounts);
此函数用于当你想要将确切的输入代币数量交换为尽可能多的输出代币,沿着路径确定的路线。路径的第一个元素是输入代币,最后一个是输出代币。
function swapTokensForExactTokens(
uint amountOut, // 给定输入资产数量,返回其他资产的最大输出数量
uint amountInMax, // 在交易回滚之前可能需要的最大输入代币数量
address[] calldata path, // 代币地址数组
address to, // 目标地址
uint deadline // Unix 时间戳,之后交易将回滚
) external returns (uint[] memory amounts); //
有关此函数每个参数的详细描述,请查看 Uniswap V2 参考词汇表 。
既然我们已经熟悉了 Uniswap 及其交换技术,让我们通过编程方式交换代币。我们首先需要设置一个主网节点。虽然我们可以运行自己的节点,但在 QuickNode,我们让启动区块链节点变得快速而简单。你可以在这里注册一个免费账户,并查看定价这里。启动节点后,检索 HTTP URL。它应该如下所示:
请记住,如果你想交换的代币没有流动性,则必须创建一个具有流动性的对,然后才能进行交换。
是时候进行我们的交换了!我们将在代码示例中将 ETH 交换为 DAI。让我们从初始化项目开始。打开终端窗口并运行以下命令来创建项目目录:
mkdir swapTokensWithEthers && cd swapTokensWithEthers && mkdir abis && npm init -y
然后,运行此命令以创建所需的文件:
touch .secret && touch index.js && touch ./abis/router.json
安装所需的依赖项:
npm i ethers@5 @uniswap/sdk
在继续下一步之前,完成以下三个步骤:
现在我们将介绍以编程方式交换代币所需的代码。在你选择的编辑器中打开 swapTokensWithEthers 目录并导航到 index.js 文件。按顺序添加以下代码片段以完成脚本。
导入依赖项
我们首先需要为我们的项目添加必要的依赖项。我们将导入 ethers 库以与智能合约交互,并导入 Uniswap SDK 以获取和创建我们的交换结构。确保你使用的是 5.7 或更早版本。fs 和 utils 库在读取或修改数据时会很有帮助。
const { ethers } = require("ethers")
const UNISWAP = require("@uniswap/sdk")
const fs = require('fs');
const { Token, WETH, Fetcher, Route, Trade, TokenAmount, TradeType, Percent} = require("@uniswap/sdk");
const { getAddress } = require("ethers/lib/utils");
配置基础设施提供者
使用我们的 QuickNode HTTP URL 和 Ethers,我们将实例化一个 Provider 对象,代表我们与区块链的通信。
const QUICKNODE_HTTP_ENDPOINT = "YOUR_QUICKNODE_HTTP_URL"
let provider = new ethers.providers.getDefaultProvider(QUICKNODE_HTTP_ENDPOINT)
导入钱包
要导入我们将用于交换代币的账户,请使用 fs 模块从 .secret 文件中读取,然后使用 Ethers 实例化一个 Wallet 对象。
const privateKey = fs.readFileSync(".secret").toString().trim()
const wallet = new ethers.Wallet(privateKey, provider)
实例化 Router 合约
Ethers 有一个 Contract 模块,我们可以使用它来实例化智能合约的实例。要创建 Router 合约的实例,我们需要输入智能合约地址、ABI 和 Provider 对象。
UNISWAP_ROUTER_ADDRESS = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
UNISWAP_ROUTER_ABI = fs.readFileSync("./abis/router.json").toString()
UNISWAP_ROUTER_CONTRACT = new ethers.Contract(UNISWAP_ROUTER_ADDRESS, UNISWAP_ROUTER_ABI, provider)
导入代币数据
要导入代币数据,我们需要创建 Token 类的实例并传入所需的输入,例如链 ID、智能合约地址和小数位数。请注意,合约地址和小数位数可能因代币而异。
const DAI = new Token(
UNISWAP.ChainId.MAINNET,
"0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
18
);
我们不需要创建 WETH 实例,因为 SDK 有一个方便的模块,我们可以使用它来返回以太坊主网上的 WETH 地址。
将 ETH 交换为代币
我们的核心逻辑将位于一个 swapTokens() 函数中,该函数将接受两个代币作为参数以及要交换的金额。请注意,滑点金额默认设置为 %0.50。请参阅函数中的注释以了解每行代码的作用。
async function swapTokens(token1, token2, amount, slippage = "50") {
try {
const pair = await Fetcher.fetchPairData(token1, token2, provider); //创建对的实例
const route = await new Route([pair], token2); // 从输入代币到输出代币的完整路径
let amountIn = ethers.utils.parseEther(amount.toString()); //将 ETH 转换为 Wei 的辅助函数
amountIn = amountIn.toString()
const slippageTolerance = new Percent(slippage, "10000"); // 50 bips,或 0.50% - 滑点容忍度
const trade = new Trade( //创建交换交易所需的信息
route,
new TokenAmount(token2, amountIn),
TradeType.EXACT_INPUT
);
const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw; // 需要转换为例如 hex
const amountOutMinHex = ethers.BigNumber.from(amountOutMin.toString()).toHexString();
const path = [token2.address, token1.address]; //代币地址数组
const to = wallet.address; // 应该是一个校验和的目标地址
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 从当前 Unix 时间开始的 20 分钟
const value = trade.inputAmount.raw; // 需要转换为例如 hex
const valueHex = await ethers.BigNumber.from(value.toString()).toHexString(); //转换为 hex 字符串
//返回 transactionRequest 的副本,默认实现调用 checkTransaction 并解析为如果是 ENS 名称,则根据 Signer 上的相关操作添加 gasPrice、nonce、gasLimit 和 chainId
const rawTxn = await UNISWAP_ROUTER_CONTRACT.populateTransaction.swapExactETHForTokens(amountOutMinHex, path, to, deadline, {
value: valueHex
})
//返回一个解析为交易的 Promise
let sendTxn = (await wallet).sendTransaction(rawTxn)
//一旦交易被包含在链中 x 个确认块中,解析为 TransactionReceipt
let reciept = (await sendTxn).wait()
//记录交易被挖掘的信息
if (reciept) {
console.log(" - Transaction is mined - " + '\n' +
"Transaction Hash:", (await sendTxn).hash +
'\n' + "Block Number: " +
(await reciept).blockNumber + '\n' +
"Navigate to https://etherscan.io/tx/" +
(await sendTxn).hash, "to see your transaction")
} else {
console.log("Error submitting transaction")
}
} catch (e) {
console.log(e)
}
}
一旦我们将所有代码编译到我们的 index.js 文件中,我们需要调用该函数以使我们的交换逻辑执行。为此,请在脚本底部添加以下代码行:
swapTokens(DAI, WETH[DAI.chainId], .00420) //第一个参数 = 我们想要的代币,第二个 = 我们拥有的代币,我们想要的金额
保存文件并在项目的主目录中运行以下命令以执行交换:
node index.js
终端输出应如下所示:
swapTokensWithEthers % node index.js
- Transaction is mined -
Transaction Hash: 0xaf6e8e358b9d93ead36b5852c4ebb9127fa88e3f7753f73d8a3f74a552601742
Block Number: 20012198
Navigate to https://etherscan.io/tx/0xaf6e8e358b9d93ead36b5852c4ebb9127fa88e3f7753f73d8a3f74a552601742 to see your transaction
我们可以通过在区块浏览器中查找哈希来确认交易成功:
如果你想在其他链(如 Optimism 或 Polygon)上进行此交换,你需要替换代币和路由器地址,因为它们在不同链上可能有不同的地址。你还需要访问每个链的节点端点。请记住,每个区块链都有自己的用于支付交易的原生代币。
今天的指南到此结束。回顾一下,本指南简要介绍了 Uniswap V2 的机制,并演示了如何使用 Javascript 和 Ethers.js 库交换代币。订阅我们的新闻通讯以获取更多关于以太坊的文章和指南。如果你有任何反馈,请通过Twitter联系我们。你还可以在我们的Discord社区服务器上与我们聊天,那里有一些你会遇到的最酷的开发者 :)
让我们知道 如果你有任何反馈或新主题的请求。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/def...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!