Created
July 23, 2023 20:04
-
-
Save BlockmanCodes/52ede9384fd774ed18c5dea5912fbe3d to your computer and use it in GitHub Desktop.
Uniswap: Universal Router: ETH to USDC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"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" | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>" | |
} | |
} | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} |
No it is not possible we need to wrap it to WETH first.
Invariant failed: WRAPPED . cannot run this code on bsc after modified params.
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
are you able to swap from a ERC20 to Eth with the universal router?