Skip to content

Instantly share code, notes, and snippets.

@claytantor
Last active October 28, 2024 23:48
Show Gist options
  • Select an option

  • Save claytantor/89cbc6f40d30a2a268cdfcc4c66f2cf0 to your computer and use it in GitHub Desktop.

Select an option

Save claytantor/89cbc6f40d30a2a268cdfcc4c66f2cf0 to your computer and use it in GitHub Desktop.
Swap Example for V3
const hre = require("hardhat");
async function main() {
console.log("Deploying SwapExampleV3 contract...",hre.ethers.version);
// SwapRouter02
// mainnet 0x2626664c2603336E57B271c5C0b26F421741e481
// sepolia 0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
// Get the contract factory and deploy the contract with the router address as a parameter
const SwapExampleV3 = await hre.ethers.getContractFactory("SwapExampleV3");
const swapExample = await SwapExampleV3.deploy();
// Wait for the contract deployment to be mined
await swapExample.deployed();
// Display the contract address
console.log("Contract deployed to address:", swapExample.address);
}
// Run the script and handle errors
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
const ethers = require("ethers");
const dotenv = require("dotenv");
const hre = require("hardhat");
if (process.argv.length < 3) {
console.error("Please provide the environment file path as an argument.");
process.exit(1);
} else {
dotenv.config({ path: process.argv[2] });
}
const {
ETHERS_NETWORK,
INFURA_PROJECT_ID,
FROM_ADDRESS_PRIVATE_KEY,
SWAP_CONTRACT_ADDRESS,
} = process.env;
async function main() {
console.log("ethers:", ethers.version);
console.log("ETHERS_NETWORK:", ETHERS_NETWORK);
console.log("INFURA_PROJECT_ID:", INFURA_PROJECT_ID);
// Set up the contract factory and provider
const provider = new ethers.providers.InfuraProvider(ETHERS_NETWORK, INFURA_PROJECT_ID);
const wallet = new ethers.Wallet(FROM_ADDRESS_PRIVATE_KEY, provider);
const balance = await wallet.getBalance();
console.log("Current ETH balance:", ethers.utils.formatEther(balance));
// Load the contract
const SwapExampleV3 = await hre.ethers.getContractFactory("SwapExampleV3", wallet);
const swapExampleV3 = await SwapExampleV3.attach(SWAP_CONTRACT_ADDRESS);
if (balance.lt(ethers.utils.parseUnits("0.05", "ether"))) {
console.error("Insufficient ETH balance for gas. Please add funds to the wallet.");
return;
}
// Call the swap function
const amountIn = ethers.utils.parseUnits("3.0", 6); // Adjust based on the token's decimals
console.log("amountIn:", amountIn);
try {
// Call the `swapExactInputSingle` function
const gasLimitMax = 500000;
const tx = await swapExampleV3.swapExactInputSingle(amountIn, {
gasLimit: gasLimitMax // Adjust the value as needed
});
console.log("Transaction hash:", tx.hash);
// Wait for the transaction to be confirmed
const receipt = await tx.wait();
console.log("Transaction confirmed in block:", receipt.blockNumber);
// Log the amountOut from the event or receipt
console.log("Transaction completed. Output received:", receipt);
} catch (error) {
console.error("Error during swap:", error);
}
}
main().catch((error) => {
console.error("Error in main:", error);
process.exit(1);
});
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity = 0.7.6;
pragma abicoder v2;
// import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/swap-router-contracts/contracts/interfaces/ISwapRouter02.sol';
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount)
external
returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
}
contract SwapExampleV3 {
// 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.
// https://docs.uniswap.org/contracts/v3/reference/deployments/ethereum-deployments
address public constant routerAddressMainnet =
0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
address public constant routerAddressSepolia =
0x3bFA4769FB09eefC5a80d6E87c3B9C650f7Ae48E;
ISwapRouter02 public immutable swapRouter = ISwapRouter02(routerAddressSepolia);
// on Sepolia testnet
address public constant USDC = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238;
address public constant WETH = 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14;
IERC20 public usdcToken = IERC20(USDC);
// For this example, we will set the pool fee to 0.3%.
uint24 public constant poolFee = 3000;
// constructor(ISwapRouter _swapRouter) {
// swapRouter = _swapRouter;
// }
constructor() {}
/// @notice swapExactInputSingle swaps a fixed amount of USDC for a maximum possible amount of WETH
/// using the USDC/WETH 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 USDC for this function to succeed.
/// @param amountIn The exact amount of USDC that will be swapped for WETH.
/// @return amountOut The amount of WETH received.
function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
usdcToken.approve(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
ISwapRouter02.ExactInputSingleParams memory params =
ISwapRouter02.ExactInputSingleParams({
tokenIn: USDC,
tokenOut: WETH,
fee: poolFee,
recipient: address(this),
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 USDC for a fixed amount of WETH.
/// @dev The calling address must approve this contract to spend its USDC for this function to succeed. As the amount of input USDC is variable,
/// the calling address will need to approve for a slightly higher amount, anticipating some variance.
/// @param amountOut The exact amount of WETH to receive from the swap.
/// @param amountInMaximum The amount of USDC we are willing to spend to receive the specified amount of WETH.
/// @return amountIn The amount of USDC actually spent in the swap.
function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
// Transfer the specified amount of USDC to this contract.
// TransferHelper.safeTransferFrom(USDC, msg.sender, address(this), amountInMaximum);
// Approve the router to spend the specifed `amountInMaximum` of USDC.
// In production, you should choose the maximum amount to spend based on oracles or other data sources to acheive a better swap.
// TransferHelper.safeApprove(USDC, address(swapRouter), amountInMaximum);
usdcToken.approve(address(swapRouter), amountInMaximum);
ISwapRouter02.ExactInputSingle memory params =
ISwapRouter02.ExactOutputSingleParams({
tokenIn: USDC,
tokenOut: WETH,
fee: poolFee,
recipient: address(this),
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) {
usdcToken.approve(address(swapRouter), 0);
usdcToken.transfer(address(this), amountInMaximum - amountIn);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment