单笔交换
交换是与 SwapX 协议最常见的交互。以下示例向您展示如何使用您创建的两个函数来实现单路径交换合约:
- swapExactInputSingle
- swapExactOutputSingle
该swapExactInputSingle函数用于执行精确输入交换,即将固定数量的一种代币交换为最大可能数量的另一种代币。此函数使用ISwapRouter接口中的ExactInputSingleParams结构和函数。
该swapExactOutputSingle函数用于执行精确输出交换,即将最小可能数量的一种代币交换为固定数量的另一种代币。此函数使用ISwapRouter接口中的ExactOutputSingleParams结构和函数。
为了简化,示例对代币合约地址进行了硬编码,但如下文进一步解释的那样,可以修改合约以根据每个交易更改池和代币。
当通过智能合约进行交易时,最重要的是要记住需要访问外部价格来源。如果没有这个,交易可能会造成相当大的损失。
注意:交换示例不是可用于生产的代码,并且以简单的方式实现以用于学习目的。
签订 声明用于编译合约的 solidity 版本,并abicoder v2允许在 calldata 中对任意嵌套数组和结构进行编码和解码,这是执行交换时使用的功能。
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
创建一个名为的合约SwapExamples,并声明一个swapRouter类型的不可变公共变量ISwapRouter。这使我们能够调用ISwapRouter接口中的函数。
contract SwapExamples {
// For the scope of these swap examples,
// we will detail the design considerations when using `exactInput`, `exactInputSingle`, `exactOutput`, and `exactOutputSingle`.
// It should be noted that for the sake of these examples we pass in the swap router as a constructor argument instead of inheriting it.
// More advanced example contracts will detail how to inherit the swap router safely.
// This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.
ISwapRouter public immutable swapRouter;
为示例硬编码代币合约地址和池费用等级。在生产中,您可能会为此使用输入参数并将输入传递到内存变量中,从而允许合约根据每笔交易更改与之交互的池和代币,但为了概念简单起见,我们在这里对其进行硬编码。
address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
// For this example, we will set the pool fee to 0.3%.
uint24 public constant poolFee = 3000;
constructor(ISwapRouter _swapRouter) {
swapRouter = _swapRouter;
}
精确输入
调用者必须批准approve合约从调用地址的账户中提取代币才能执行交换。请记住,因为我们的合约本身就是合约,而不是调用者(我们)的扩展;我们还必须批准 SwapX 协议路由器合约,以便在从调用地址(我们)提取代币后使用我们的合约将拥有的代币。
然后,将amountDai 的 从调用地址转移到我们的合约中,并用作amount传递给第二个的值approve。
/// @notice swapExactInputSingle swaps a fixed amount of DAI for a maximum possible amount of WETH9
/// using the DAI/WETH9 0.3% pool by calling `exactInputSingle` in the swap router.
/// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
/// @param amountIn The exact amount of DAI that will be swapped for WETH9.
/// @return amountOut The amount of WETH9 received.
function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
// msg.sender must approve this contract
// Transfer the specified amount of DAI to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);
// Approve the router to spend DAI.
TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);
交换输入
要执行交换功能,我们需要填充ExactInputSingleParams必要的交换数据。这些参数可以在智能合约接口中找到,可以在此处浏览。
参数简要概述:
- tokenIn入站代币的合约地址
- tokenOut出站代币的合约地址
- fee池的费用等级,用于确定执行交换的正确池合约
- recipient出站令牌的目标地址
- deadline:unix 时间,超过该时间后交换将失败,以防止长期未决的交易和价格剧烈波动
- amountOutMinimum:我们将其设置为零,但这在生产中存在重大风险。对于实际部署,应使用我们的 SDK 或链上价格预言机来计算此值 - 这有助于防止由于抢先交易三明治或其他类型的价格操纵而导致交易价格异常糟糕
- sqrtPriceLimitX96:我们将其设置为零 - 这使得此参数处于非活动状态。在生产中,此值可用于设置交换将推动池的价格限制,这有助于防止价格影响或设置各种与价格相关的机制中的逻辑。
调用
// Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.
// We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.
ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({
tokenIn: DAI,
tokenOut: WETH9,
fee: poolFee,
recipient: msg.sender,
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
// The call to `exactInputSingle` executes the swap.
amountOut = swapRouter.exactInputSingle(params);
}
精确输出
精确输出将输入代币的最小可能数量与输出代币的固定数量进行交换。这是不太常见的交换方式 - 但在各种情况下都很有用。
因为此示例在预期交换的情况下转移了入站资产 - 所以在执行交换后可能会剩下一些入站代币,这就是为什么我们在交换结束时将其返还给调用地址。
调用
/// @notice swapExactOutputSingle swaps a minimum possible amount of DAI for a fixed amount of WXOC.
/// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
/// the calling address will need to approve for a slightly higher amount, anticipating some variance.
/// @param amountOut The exact amount of WETH9 to receive from the swap.
/// @param amountInMaximum The amount of DAI we are willing to spend to receive the specified amount of WETH9.
/// @return amountIn The amount of DAI actually spent in the swap.
function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
// Transfer the specified amount of DAI to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
// Approve the router to spend the specified `amountInMaximum` of DAI.
// In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.
TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);
ISwapRouter.ExactOutputSingleParams memory params =
ISwapRouter.ExactOutputSingleParams({
tokenIn: DAI,
tokenOut: WETH9,
fee: poolFee,
recipient: msg.sender,
deadline: block.timestamp,
amountOut: amountOut,
amountInMaximum: amountInMaximum,
sqrtPriceLimitX96: 0
});
// Executes the swap returning the amountIn needed to spend to receive the desired amountOut.
amountIn = swapRouter.exactOutputSingle(params);
// For exact output swaps, the amountInMaximum may not have all been spent.
// If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
if (amountIn < amountInMaximum) {
TransferHelper.safeApprove(DAI, address(swapRouter), 0);
TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
}
}
完整的单一交换
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
contract SwapExamples {
// For the scope of these swap examples,
// we will detail the design considerations when using
// `exactInput`, `exactInputSingle`, `exactOutput`, and `exactOutputSingle`.
// It should be noted that for the sake of these examples, we purposefully pass in the swap router instead of inherit the swap router for simplicity.
// More advanced example contracts will detail how to inherit the swap router safely.
ISwapRouter public immutable swapRouter;
// This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.
address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
// For this example, we will set the pool fee to 0.3%.
uint24 public constant poolFee = 3000;
constructor(ISwapRouter _swapRouter) {
swapRouter = _swapRouter;
}
/// @notice swapExactInputSingle swaps a fixed amount of DAI for a maximum possible amount of WETH9
/// using the DAI/WETH9 0.3% pool by calling `exactInputSingle` in the swap router.
/// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
/// @param amountIn The exact amount of DAI that will be swapped for WETH9.
/// @return amountOut The amount of WETH9 received.
function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
// msg.sender must approve this contract
// Transfer the specified amount of DAI to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);
// Approve the router to spend DAI.
TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);
// Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.
// We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.
ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({
tokenIn: DAI,
tokenOut: WETH9,
fee: poolFee,
recipient: msg.sender,
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
// The call to `exactInputSingle` executes the swap.
amountOut = swapRouter.exactInputSingle(params);
}
/// @notice swapExactOutputSingle swaps a minimum possible amount of DAI for a fixed amount of WXOC.
/// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
/// the calling address will need to approve for a slightly higher amount, anticipating some variance.
/// @param amountOut The exact amount of WETH9 to receive from the swap.
/// @param amountInMaximum The amount of DAI we are willing to spend to receive the specified amount of WETH9.
/// @return amountIn The amount of DAI actually spent in the swap.
function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
// Transfer the specified amount of DAI to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
// Approve the router to spend the specifed `amountInMaximum` of DAI.
// In production, you should choose the maximum amount to spend based on oracles or other data sources to acheive a better swap.
TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);
ISwapRouter.ExactOutputSingleParams memory params =
ISwapRouter.ExactOutputSingleParams({
tokenIn: DAI,
tokenOut: WETH9,
fee: poolFee,
recipient: msg.sender,
deadline: block.timestamp,
amountOut: amountOut,
amountInMaximum: amountInMaximum,
sqrtPriceLimitX96: 0
});
// Executes the swap returning the amountIn needed to spend to receive the desired amountOut.
amountIn = swapRouter.exactOutputSingle(params);
// For exact output swaps, the amountInMaximum may not have all been spent.
// If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
if (amountIn < amountInMaximum) {
TransferHelper.safeApprove(DAI, address(swapRouter), 0);
TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
}
}
}