Skip to content

Instantly share code, notes, and snippets.

@BlockmanCodes
Created May 31, 2023 12:24
Show Gist options
  • Save BlockmanCodes/f577138541c06dfa67404d8e9b93b962 to your computer and use it in GitHub Desktop.
Save BlockmanCodes/f577138541c06dfa67404d8e9b93b962 to your computer and use it in GitHub Desktop.
Uniswap V3: protocol fees
const { ContractFactory, utils } = require("ethers")
const WETH9 = require("../WETH9.json")
const fs = require('fs');
const { promisify } = require('util');
const artifacts = {
UniswapV3Factory: require("@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json"),
SwapRouter: require("@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json"),
NFTDescriptor: require("@uniswap/v3-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json"),
NonfungibleTokenPositionDescriptor: require("@uniswap/v3-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json"),
NonfungiblePositionManager: require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"),
WETH9,
};
const linkLibraries = ({ bytecode, linkReferences }, libraries) => {
Object.keys(linkReferences).forEach((fileName) => {
Object.keys(linkReferences[fileName]).forEach((contractName) => {
if (!libraries.hasOwnProperty(contractName)) {
throw new Error(`Missing link library name ${contractName}`)
}
const address = utils
.getAddress(libraries[contractName])
.toLowerCase()
.slice(2)
linkReferences[fileName][contractName].forEach(
({ start, length }) => {
const start2 = 2 + start * 2
const length2 = length * 2
bytecode = bytecode
.slice(0, start2)
.concat(address)
.concat(bytecode.slice(start2 + length2, bytecode.length))
}
)
})
})
return bytecode
}
async function main() {
const [owner] = await ethers.getSigners();
Weth = new ContractFactory(artifacts.WETH9.abi, artifacts.WETH9.bytecode, owner);
weth = await Weth.deploy();
Factory = new ContractFactory(artifacts.UniswapV3Factory.abi, artifacts.UniswapV3Factory.bytecode, owner);
factory = await Factory.deploy();
SwapRouter = new ContractFactory(artifacts.SwapRouter.abi, artifacts.SwapRouter.bytecode, owner);
swapRouter = await SwapRouter.deploy(factory.address, weth.address);
NFTDescriptor = new ContractFactory(artifacts.NFTDescriptor.abi, artifacts.NFTDescriptor.bytecode, owner);
nftDescriptor = await NFTDescriptor.deploy();
const linkedBytecode = linkLibraries(
{
bytecode: artifacts.NonfungibleTokenPositionDescriptor.bytecode,
linkReferences: {
"NFTDescriptor.sol": {
NFTDescriptor: [
{
length: 20,
start: 1681,
},
],
},
},
},
{
NFTDescriptor: nftDescriptor.address,
}
);
NonfungibleTokenPositionDescriptor = new ContractFactory(artifacts.NonfungibleTokenPositionDescriptor.abi, linkedBytecode, owner);
const nativeCurrencyLabelBytes = utils.formatBytes32String('WETH')
nonfungibleTokenPositionDescriptor = await NonfungibleTokenPositionDescriptor.deploy(weth.address, nativeCurrencyLabelBytes);
NonfungiblePositionManager = new ContractFactory(artifacts.NonfungiblePositionManager.abi, artifacts.NonfungiblePositionManager.bytecode, owner);
nonfungiblePositionManager = await NonfungiblePositionManager.deploy(factory.address, weth.address, nonfungibleTokenPositionDescriptor.address);
let addresses = [
`WETH_ADDRESS=${weth.address}`,
`FACTORY_ADDRESS=${factory.address}`,
`SWAP_ROUTER_ADDRESS=${swapRouter.address}`,
`NFT_DESCRIPTOR_ADDRESS=${nftDescriptor.address}`,
`POSITION_DESCRIPTOR_ADDRESS=${nonfungibleTokenPositionDescriptor.address}`,
`POSITION_MANAGER_ADDRESS=${nonfungiblePositionManager.address}`,
]
const data = addresses.join('\n')
const writeFile = promisify(fs.writeFile);
const filePath = '.env';
return writeFile(filePath, data)
.then(() => {
console.log('Addresses recorded.');
})
.catch((error) => {
console.error('Error logging addresses:', error);
throw error;
});
}
/*
npx hardhat run --network localhost scripts/01_deployContracts.js
*/
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
const fs = require('fs');
const { promisify } = require('util');
async function main() {
const [owner, signer2] = await ethers.getSigners();
const Tether = await ethers.getContractFactory('Tether', owner);
const tether = await Tether.deploy();
const Usdc = await ethers.getContractFactory('UsdCoin', owner);
const usdc = await Usdc.deploy();
const WrappedBitcoin = await ethers.getContractFactory('WrappedBitcoin', owner);
const wrappedBitcoin = await WrappedBitcoin.deploy();
await tether.connect(owner).mint(
signer2.address,
ethers.utils.parseEther('100000')
)
await usdc.connect(owner).mint(
signer2.address,
ethers.utils.parseEther('100000')
)
await wrappedBitcoin.connect(owner).mint(
signer2.address,
ethers.utils.parseEther('100000')
)
let addresses = [
`USDC_ADDRESS=${usdc.address}`,
`TETHER_ADDRESS=${tether.address}`,
`WRAPPED_BITCOIN_ADDRESS=${wrappedBitcoin.address}`,
]
const data = '\n' + addresses.join('\n')
const writeFile = promisify(fs.appendFile);
const filePath = '.env';
return writeFile(filePath, data)
.then(() => {
console.log('Addresses recorded.');
})
.catch((error) => {
console.error('Error logging addresses:', error);
throw error;
});
}
/*
npx hardhat run --network localhost scripts/02_deployTokens.js
*/
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
require('dotenv').config()
TETHER_ADDRESS = process.env.TETHER_ADDRESS
USDC_ADDRESS = process.env.USDC_ADDRESS
FACTORY_ADDRESS = process.env.FACTORY_ADDRESS
POSITION_MANAGER_ADDRESS = process.env.POSITION_MANAGER_ADDRESS
const artifacts = {
UniswapV3Factory: require("@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json"),
NonfungiblePositionManager: require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"),
UniswapV3Pool: require("@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json"),
};
const { Contract, BigNumber } = require("ethers")
const bn = require('bignumber.js')
const {promisify} = require("util");
const fs = require("fs");
bn.config({ EXPONENTIAL_AT: 999999, DECIMAL_PLACES: 40 })
const provider = ethers.provider
function encodePriceSqrt(reserve1, reserve0) {
return BigNumber.from(
new bn(reserve1.toString())
.div(reserve0.toString())
.sqrt()
.multipliedBy(new bn(2).pow(96))
.integerValue(3)
.toString()
)
}
const nonfungiblePositionManager = new Contract(
POSITION_MANAGER_ADDRESS,
artifacts.NonfungiblePositionManager.abi,
provider
)
const factory = new Contract(
FACTORY_ADDRESS,
artifacts.UniswapV3Factory.abi,
provider
)
async function deployPool(token0, token1, fee, price) {
const [owner] = await ethers.getSigners();
await nonfungiblePositionManager.connect(owner).createAndInitializePoolIfNecessary(
token0,
token1,
fee,
price,
{ gasLimit: 5000000 }
)
const poolAddress = await factory.connect(owner).getPool(
token0,
token1,
fee,
)
return poolAddress
}
async function main() {
const [owner] = await ethers.getSigners();
const poolAddress = await deployPool(TETHER_ADDRESS, USDC_ADDRESS,10000, encodePriceSqrt(1, 1))
const poolContract = new Contract(poolAddress, artifacts.UniswapV3Pool.abi, provider)
const feeProtocol0 = 4
const feeProtocol1 = 4
await poolContract.connect(owner).setFeeProtocol(feeProtocol0, feeProtocol1)
let addresses = [
`USDT_USDC_500=${poolAddress}`
]
const data = '\n' + addresses.join('\n')
const writeFile = promisify(fs.appendFile);
const filePath = '.env';
return writeFile(filePath, data)
.then(() => {
console.log('Addresses recorded.');
})
.catch((error) => {
console.error('Error logging addresses:', error);
throw error;
});
}
/*
npx hardhat run --network localhost scripts/03_deployPools.js
*/
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
require('dotenv').config()
TETHER_ADDRESS = process.env.TETHER_ADDRESS
USDC_ADDRESS = process.env.USDC_ADDRESS
POSITION_MANAGER_ADDRESS = process.env.POSITION_MANAGER_ADDRESS
USDT_USDC_500 = process.env.USDT_USDC_500
const artifacts = {
NonfungiblePositionManager: require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"),
Usdt: require("../artifacts/contracts/Tether.sol/Tether.json"),
Usdc: require("../artifacts/contracts/UsdCoin.sol/UsdCoin.json"),
UniswapV3Pool: require("@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json"),
};
const { Contract } = require("ethers")
const { Token } = require('@uniswap/sdk-core')
const { Pool, Position, nearestUsableTick } = require('@uniswap/v3-sdk')
async function getPoolData(poolContract) {
const [tickSpacing, fee, liquidity, slot0] = await Promise.all([
poolContract.tickSpacing(),
poolContract.fee(),
poolContract.liquidity(),
poolContract.slot0(),
])
return {
tickSpacing: tickSpacing,
fee: fee,
liquidity: liquidity,
sqrtPriceX96: slot0[0],
tick: slot0[1],
}
}
async function main() {
const [_owner, signer2] = await ethers.getSigners();
const provider = ethers.provider
const usdtContract = new Contract(TETHER_ADDRESS,artifacts.Usdt.abi,provider)
const usdcContract = new Contract(USDC_ADDRESS,artifacts.Usdc.abi,provider)
await usdtContract.connect(signer2).approve(POSITION_MANAGER_ADDRESS, ethers.utils.parseEther('10000'))
await usdcContract.connect(signer2).approve(POSITION_MANAGER_ADDRESS, ethers.utils.parseEther('10000'))
const poolContract = new Contract(USDT_USDC_500, artifacts.UniswapV3Pool.abi, provider)
const poolData = await getPoolData(poolContract)
const UsdtToken = new Token(31337, TETHER_ADDRESS, 18, 'USDT', 'Tether')
const UsdcToken = new Token(31337, USDC_ADDRESS, 18, 'USDC', 'UsdCoin')
const pool = new Pool(
UsdtToken,
UsdcToken,
poolData.fee,
poolData.sqrtPriceX96.toString(),
poolData.liquidity.toString(),
poolData.tick
)
const position = new Position({
pool: pool,
liquidity: ethers.utils.parseEther('100000'),
tickLower: nearestUsableTick(poolData.tick, poolData.tickSpacing) - poolData.tickSpacing * 2,
tickUpper: nearestUsableTick(poolData.tick, poolData.tickSpacing) + poolData.tickSpacing * 2,
})
const { amount0: amount0Desired, amount1: amount1Desired} = position.mintAmounts
const params = {
token0: TETHER_ADDRESS,
token1: USDC_ADDRESS,
fee: poolData.fee,
tickLower: nearestUsableTick(poolData.tick, poolData.tickSpacing) - poolData.tickSpacing * 2,
tickUpper: nearestUsableTick(poolData.tick, poolData.tickSpacing) + poolData.tickSpacing * 2,
amount0Desired: amount0Desired.toString(),
amount1Desired: amount1Desired.toString(),
amount0Min: 0,
amount1Min: 0,
recipient: signer2.address,
deadline: Math.floor(Date.now() / 1000) + (60 * 10)
}
const nonfungiblePositionManager = new Contract(
POSITION_MANAGER_ADDRESS,
artifacts.NonfungiblePositionManager.abi,
provider
)
const tx = await nonfungiblePositionManager.connect(signer2).mint(
params,
{ gasLimit: '1000000' }
)
await tx.wait()
}
/*
npx hardhat run --network localhost scripts/04_addLiquidity.js
*/
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
require('dotenv').config()
USDT_USDC_500 = process.env.USDT_USDC_500
const { Contract } = require("ethers")
const UniswapV3Pool = require("@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json")
async function getPoolData(poolContract) {
const [tickSpacing, fee, liquidity, slot0, protocolFees] = await Promise.all([
poolContract.tickSpacing(),
poolContract.fee(),
poolContract.liquidity(),
poolContract.slot0(),
poolContract.protocolFees(),
])
return {
tickSpacing: tickSpacing,
fee: fee,
liquidity: liquidity.toString(),
sqrtPriceX96: slot0[0],
tick: slot0[1],
protocolFees: protocolFees,
}
}
async function main() {
const provider = ethers.provider
const poolContract = new Contract(USDT_USDC_500, UniswapV3Pool.abi, provider)
const poolData = await getPoolData(poolContract)
console.log('poolData', poolData)
}
/*
npx hardhat run --network localhost scripts/05_poolData.js
*/
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
const { Contract } = require("ethers")
require('dotenv').config()
TETHER_ADDRESS = process.env.TETHER_ADDRESS
USDC_ADDRESS = process.env.USDC_ADDRESS
SWAP_ROUTER_ADDRESS = process.env.SWAP_ROUTER_ADDRESS
USDT_USDC_500 = process.env.USDT_USDC_500
const artifacts = {
SwapRouter: require("@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json"),
Usdt: require("../artifacts/contracts/Tether.sol/Tether.json"),
Usdc: require("../artifacts/contracts/UsdCoin.sol/UsdCoin.json"),
UniswapV3Pool: require("@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json"),
};
async function getPoolData(poolContract) {
const [token0, token1, fee] = await Promise.all([
poolContract.token0(),
poolContract.token1(),
poolContract.fee(),
])
return {
token0: token0,
token1: token1,
fee: fee,
}
}
async function main() {
const provider = ethers.provider;
const [_owner, signer2] = await ethers.getSigners();
const usdtContract = new Contract(TETHER_ADDRESS,artifacts.Usdt.abi,provider)
const usdcContract = new Contract(USDC_ADDRESS,artifacts.Usdc.abi,provider)
const poolContract = new ethers.Contract(
USDT_USDC_500,
artifacts.UniswapV3Pool.abi,
provider
);
const poolData = await getPoolData(poolContract)
const swapRouterContract = new ethers.Contract(
SWAP_ROUTER_ADDRESS,
artifacts.SwapRouter.abi,
provider
)
const amountIn = ethers.utils.parseUnits('100')
const params = {
tokenIn: poolData.token0,
tokenOut: poolData.token1,
fee: poolData.fee,
recipient: signer2.address,
deadline: Math.floor(Date.now() / 1000) + (60 * 10),
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0,
}
await usdtContract.connect(signer2).approve(SWAP_ROUTER_ADDRESS, amountIn)
await usdcContract.connect(signer2).approve(SWAP_ROUTER_ADDRESS, amountIn)
const tx = await swapRouterContract.connect(signer2).exactInputSingle(
params,
{ gasLimit: ethers.utils.hexlify(1000000) }
)
await tx.wait()
}
/*
npx hardhat run --network localhost scripts/06_swapTokens.js
*/
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
require('dotenv').config()
USDT_USDC_500 = process.env.USDT_USDC_500
const artifacts = {
UniswapV3Pool: require("@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json"),
};
const { Contract } = require("ethers")
async function main() {
const [owner] = await ethers.getSigners();
const provider = ethers.provider
const poolContract = new Contract(USDT_USDC_500, artifacts.UniswapV3Pool.abi, provider)
const withdrawAmount = ethers.utils.parseEther('100000')
await poolContract.connect(owner).collectProtocol(
owner.address,
withdrawAmount,
withdrawAmount,
)
}
/*
npx hardhat run --network localhost scripts/07_withdrawProtocolFees.js
*/
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import "@openzeppelin/contracts/access/Ownable.sol";
contract Tether is ERC20, Ownable {
constructor() ERC20('Tether', 'USDT') {}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import "@openzeppelin/contracts/access/Ownable.sol";
contract UsdCoin is ERC20, Ownable {
constructor() ERC20('UsdCoin', 'USDC') {}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import "@openzeppelin/contracts/access/Ownable.sol";
contract WrappedBitcoin is ERC20, Ownable {
constructor() ERC20('WrappedBitcoin', 'WBTC') {}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
require("@nomicfoundation/hardhat-toolbox")
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.18",
settings: {
optimizer: {
enabled: true,
runs: 5000,
details: { yul: false },
},
}
},
};
{
"name": "local-deploy-uniswap-v3-updated",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^1.0.6",
"@nomicfoundation/hardhat-network-helpers": "^1.0.8",
"@nomicfoundation/hardhat-toolbox": "^2.0.2",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@openzeppelin/contracts": "^4.8.3",
"@typechain/ethers-v5": "^10.2.1",
"@typechain/hardhat": "^6.1.6",
"@types/chai": "^4.3.5",
"@types/mocha": "^10.0.1",
"@uniswap/v3-periphery": "^1.4.3",
"@uniswap/v3-sdk": "^3.9.0",
"bignumber.js": "^9.1.1",
"chai": "^4.3.7",
"dotenv": "^16.0.3",
"hardhat": "^2.14.0",
"hardhat-gas-reporter": "^1.0.9",
"solidity-coverage": "^0.8.2",
"ts-node": "^10.9.1",
"typechain": "^8.1.1",
"typescript": "^5.0.4"
},
"devDependencies": {}
}
@r-tom90
Copy link

r-tom90 commented Jun 1, 2023

Great content as always :) I'm guessing all that has to be changed when deploying live would be to change to mainnet in hardhat config and also of course change the tokens required, ie getting the eth address itself as I see 3 addresses there, how would I add native eth address? Thanks

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