Skip to content

Instantly share code, notes, and snippets.

@BlockmanCodes
Created July 23, 2023 20:04
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save BlockmanCodes/52ede9384fd774ed18c5dea5912fbe3d to your computer and use it in GitHub Desktop.
Save BlockmanCodes/52ede9384fd774ed18c5dea5912fbe3d to your computer and use it in GitHub Desktop.
Uniswap: Universal Router: ETH to USDC
const { SwapRouter } = require('@uniswap/universal-router-sdk')
const { TradeType, Ether, Token, CurrencyAmount, Percent } = require('@uniswap/sdk-core')
const { Trade: V2Trade } = require('@uniswap/v2-sdk')
const { Pool, nearestUsableTick, TickMath, TICK_SPACINGS, FeeAmount, Trade: V3Trade, Route: RouteV3 } = require('@uniswap/v3-sdk')
const { MixedRouteTrade, Trade: RouterTrade } = require('@uniswap/router-sdk')
const IUniswapV3Pool = require('@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json')
const JSBI = require('jsbi')
const erc20Abi = require('../abis/erc20.json')
const hardhat = require("hardhat");
const provider = hardhat.ethers.provider;
const ETHER = Ether.onChain(1)
const WETH = new Token(1, '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', 18, 'WETH', 'Wrapped Ether')
const USDC = new Token(1, '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 6, 'USDC', 'USD Coin')
const wethContract = new hardhat.ethers.Contract(WETH.address, erc20Abi, provider)
const usdcContract = new hardhat.ethers.Contract(USDC.address, erc20Abi, provider)
async function getPool(tokenA, tokenB, feeAmount) {
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
const poolAddress = Pool.getAddress(token0, token1, feeAmount)
const contract = new hardhat.ethers.Contract(poolAddress, IUniswapV3Pool.abi, provider)
let liquidity = await contract.liquidity()
let { sqrtPriceX96, tick } = await contract.slot0()
liquidity = JSBI.BigInt(liquidity.toString())
sqrtPriceX96 = JSBI.BigInt(sqrtPriceX96.toString())
return new Pool(token0, token1, feeAmount, sqrtPriceX96, liquidity, tick, [
{
index: nearestUsableTick(TickMath.MIN_TICK, TICK_SPACINGS[feeAmount]),
liquidityNet: liquidity,
liquidityGross: liquidity,
},
{
index: nearestUsableTick(TickMath.MAX_TICK, TICK_SPACINGS[feeAmount]),
liquidityNet: JSBI.multiply(liquidity, JSBI.BigInt('-1')),
liquidityGross: liquidity,
},
])
}
function swapOptions(options) {
return Object.assign(
{
slippageTolerance: new Percent(5, 100),
recipient: RECIPIENT,
},
options
)
}
function buildTrade(trades) {
return new RouterTrade({
v2Routes: trades
.filter((trade) => trade instanceof V2Trade)
.map((trade) => ({
routev2: trade.route,
inputAmount: trade.inputAmount,
outputAmount: trade.outputAmount,
})),
v3Routes: trades
.filter((trade) => trade instanceof V3Trade)
.map((trade) => ({
routev3: trade.route,
inputAmount: trade.inputAmount,
outputAmount: trade.outputAmount,
})),
mixedRoutes: trades
.filter((trade) => trade instanceof MixedRouteTrade)
.map((trade) => ({
mixedRoute: trade.route,
inputAmount: trade.inputAmount,
outputAmount: trade.outputAmount,
})),
tradeType: trades[0].tradeType,
})
}
const RECIPIENT = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'
async function main() {
const signer = await hardhat.ethers.getImpersonatedSigner(RECIPIENT);
const WETH_USDC_V3 = await getPool(WETH, USDC, FeeAmount.MEDIUM)
const inputEther = hardhat.ethers.utils.parseEther('1').toString()
const trade = await V3Trade.fromRoute(
new RouteV3([WETH_USDC_V3], ETHER, USDC),
CurrencyAmount.fromRawAmount(ETHER, inputEther),
TradeType.EXACT_INPUT
)
const routerTrade = buildTrade([trade])
const opts = swapOptions({})
const params = SwapRouter.swapERC20CallParameters(routerTrade, opts)
let ethBalance
let wethBalance
let usdcBalance
ethBalance = await provider.getBalance(RECIPIENT)
wethBalance = await wethContract.balanceOf(RECIPIENT)
usdcBalance = await usdcContract.balanceOf(RECIPIENT)
console.log('---------------------------- BEFORE')
console.log('ethBalance', hardhat.ethers.utils.formatUnits(ethBalance, 18))
console.log('wethBalance', hardhat.ethers.utils.formatUnits(wethBalance, 18))
console.log('usdcBalance', hardhat.ethers.utils.formatUnits(usdcBalance, 6))
const tx = await signer.sendTransaction({
data: params.calldata,
to: '0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B',
value: params.value,
from: RECIPIENT,
})
const receipt = await tx.wait()
console.log('---------------------------- SUCCESS?')
console.log('status', receipt.status)
ethBalance = await provider.getBalance(RECIPIENT)
wethBalance = await wethContract.balanceOf(RECIPIENT)
usdcBalance = await usdcContract.balanceOf(RECIPIENT)
console.log('---------------------------- AFTER')
console.log('ethBalance', hardhat.ethers.utils.formatUnits(ethBalance, 18))
console.log('wethBalance', hardhat.ethers.utils.formatUnits(wethBalance, 18))
console.log('usdcBalance', hardhat.ethers.utils.formatUnits(usdcBalance, 6))
}
/*
node scripts/01_simpleSwap.js
*/
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "subtractedValue",
"type": "uint256"
}
],
"name": "decreaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "addedValue",
"type": "uint256"
}
],
"name": "increaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
require("@nomiclabs/hardhat-waffle");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.18",
networks: {
hardhat: {
forking: {
url: "https://mainnet.infura.io/v3/<YOUR_KEY>"
}
}
}
};
{
"name": "tutorials",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-waffle": "^2.0.6",
"@types/sinon-chai": "^3.2.9",
"@uniswap/sdk": "^3.0.3",
"@uniswap/sdk-core": "^4.0.2",
"@uniswap/smart-order-router": "^3.13.2-hotfix2",
"@uniswap/v3-core": "^1.0.1",
"@uniswap/v3-periphery": "^1.4.3",
"@uniswap/v3-sdk": "^3.9.0",
"ethereum-waffle": "^4.0.10",
"ethers": "^5.7.2",
"hardhat": "^2.14.0"
}
}
@ashaller2017
Copy link

are you able to swap from a ERC20 to Eth with the universal router?

@asmitbhantana
Copy link

No it is not possible we need to wrap it to WETH first.

@1500256797
Copy link

Invariant failed: WRAPPED . cannot run this code on bsc after modified params.

@kaiqiangh
Copy link

kaiqiangh commented Jan 23, 2024

got this error when swapping on V2 pool
Error: Unexpected pool type in route when constructing trade object

Here is the code to calculate the trade instance:
let route = new RouteV2([pool], nativeEth, token1)
let trade = new V2Trade(route,CurrencyAmount.fromRawAmount(nativeEth, inputEther),TradeType.EXACT_INPUT )
let routerTrade = buildTrade([trade])

what kind of V2 pool format should I use in RouterTrade? Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment