Skip to content

Instantly share code, notes, and snippets.

@BlockmanCodes
Created November 22, 2022 01:29
Show Gist options
  • Save BlockmanCodes/d0068cfc56ab67925dfd4b854ffea8fc to your computer and use it in GitHub Desktop.
Save BlockmanCodes/d0068cfc56ab67925dfd4b854ffea8fc to your computer and use it in GitHub Desktop.

Revisions

  1. BlockmanCodes created this gist Nov 22, 2022.
    95 changes: 95 additions & 0 deletions 01_deployContracts.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,95 @@
    const { Contract, ContractFactory, utils, BigNumber } = require("ethers")
    const WETH9 = require("../WETH9.json")

    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: 1261,
    },
    ],
    },
    },
    },
    {
    NFTDescriptor: nftDescriptor.address,
    }
    );

    NonfungibleTokenPositionDescriptor = new ContractFactory(artifacts.NonfungibleTokenPositionDescriptor.abi, linkedBytecode, owner);
    nonfungibleTokenPositionDescriptor = await NonfungibleTokenPositionDescriptor.deploy(weth.address);

    NonfungiblePositionManager = new ContractFactory(artifacts.NonfungiblePositionManager.abi, artifacts.NonfungiblePositionManager.bytecode, owner);
    nonfungiblePositionManager = await NonfungiblePositionManager.deploy(factory.address, weth.address, nonfungibleTokenPositionDescriptor.address);

    console.log('WETH_ADDRESS=', `'${weth.address}'`)
    console.log('FACTORY_ADDRESS=', `'${factory.address}'`)
    console.log('SWAP_ROUTER_ADDRESS=', `'${swapRouter.address}'`)
    console.log('NFT_DESCRIPTOR_ADDRESS=', `'${nftDescriptor.address}'`)
    console.log('POSITION_DESCRIPTOR_ADDRESS=', `'${nonfungibleTokenPositionDescriptor.address}'`)
    console.log('POSITION_MANAGER_ADDRESS=', `'${nonfungiblePositionManager.address}'`)
    }

    /*
    npx hardhat run --network localhost scripts/01_deployContracts.js
    */

    main()
    .then(() => process.exit(0))
    .catch((error) => {
    console.error(error);
    process.exit(1);
    });
    33 changes: 33 additions & 0 deletions 02_deployTokens.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,33 @@
    async function main() {
    const [owner, signer2] = await ethers.getSigners();

    Tether = await ethers.getContractFactory('Tether', owner);
    tether = await Tether.deploy();

    Usdc = await ethers.getContractFactory('UsdCoin', owner);
    usdc = await Usdc.deploy();

    await tether.connect(owner).mint(
    owner.address,
    ethers.utils.parseEther('100000')
    )
    await usdc.connect(owner).mint(
    owner.address,
    ethers.utils.parseEther('100000')
    )

    console.log('TETHER_ADDRESS=', `'${tether.address}'`)
    console.log('USDC_ADDRESS=', `'${usdc.address}'`)
    }

    /*
    npx hardhat run --network localhost scripts/02_deployTokens.js
    */


    main()
    .then(() => process.exit(0))
    .catch((error) => {
    console.error(error);
    process.exit(1);
    });
    83 changes: 83 additions & 0 deletions 03_deployPools.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    // Token addresses
    TETHER_ADDRESS= '0x0165878A594ca255338adfa4d48449f69242Eb8F'
    USDC_ADDRESS= '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853'

    // Uniswap contract address
    WETH_ADDRESS= '0x5FbDB2315678afecb367f032d93F642f64180aa3'
    FACTORY_ADDRESS= '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512'
    SWAP_ROUTER_ADDRESS= '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0'
    NFT_DESCRIPTOR_ADDRESS= '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9'
    POSITION_DESCRIPTOR_ADDRESS= '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9'
    POSITION_MANAGER_ADDRESS= '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707'

    const artifacts = {
    UniswapV3Factory: require("@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json"),
    NonfungiblePositionManager: require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"),
    };

    const { Contract, BigNumber } = require("ethers")
    const bn = require('bignumber.js')
    bn.config({ EXPONENTIAL_AT: 999999, DECIMAL_PLACES: 40 })

    const provider = waffle.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 usdtUsdc500 = await deployPool(TETHER_ADDRESS, USDC_ADDRESS, 500, encodePriceSqrt(1, 1))
    const usdtUsdc3000 = await deployPool(TETHER_ADDRESS, USDC_ADDRESS, 3000, encodePriceSqrt(1, 2))
    const usdtUsdc10000 = await deployPool(TETHER_ADDRESS, USDC_ADDRESS, 10000, encodePriceSqrt(2, 1))

    console.log('USDT_USDC_500=', `'${usdtUsdc500}'`)
    console.log('USDT_USDC_3000=', `'${usdtUsdc3000}'`)
    console.log('USDT_USDC_10000=', `'${usdtUsdc10000}'`)
    }

    /*
    npx hardhat run --network localhost scripts/03_deployPools.js
    */

    main()
    .then(() => process.exit(0))
    .catch((error) => {
    console.error(error);
    process.exit(1);
    });
    140 changes: 140 additions & 0 deletions 04_addLiquidity.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,140 @@
    // Uniswap contract addresses
    WETH_ADDRESS= '0x5FbDB2315678afecb367f032d93F642f64180aa3'
    FACTORY_ADDRESS= '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512'
    SWAP_ROUTER_ADDRESS= '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0'
    NFT_DESCRIPTOR_ADDRESS= '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9'
    POSITION_DESCRIPTOR_ADDRESS= '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9'
    POSITION_MANAGER_ADDRESS= '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707'

    // Pool addresses
    USDT_USDC_500= '0x1FA8DDa81477A5b6FA1b2e149e93ed9C7928992F'
    USDT_USDC_3000= '0x3B00F82071576B8489A6e3df223dcC0e937841d1'
    USDT_USDC_10000= '0xb09EB46A30889ae3cE4AFa5d8ebD136B4f389B85'

    // Token addresses
    TETHER_ADDRESS= '0x0165878A594ca255338adfa4d48449f69242Eb8F'
    USDC_ADDRESS= '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853'

    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],
    }
    }

    LIQUIDITY = ethers.utils.parseEther('100')
    DEADLINE = Math.floor(Date.now() / 1000) + (60 * 10)
    POOL_ADDRESSES = [USDT_USDC_500, USDT_USDC_3000, USDT_USDC_10000] // ,


    async function main() {
    const [owner, signer2] = await ethers.getSigners();
    const provider = waffle.provider;

    const nonfungiblePositionManager = new Contract(
    POSITION_MANAGER_ADDRESS,
    artifacts.NonfungiblePositionManager.abi,
    provider
    )
    const usdtContract = new Contract(TETHER_ADDRESS,artifacts.Usdt.abi,provider)
    const usdcContract = new Contract(USDC_ADDRESS,artifacts.Usdc.abi,provider)

    await usdtContract.connect(owner).approve(POSITION_MANAGER_ADDRESS, ethers.utils.parseEther('9999999'))
    await usdcContract.connect(owner).approve(POSITION_MANAGER_ADDRESS, ethers.utils.parseEther('9999999'))

    const UsdtToken = new Token(31337, TETHER_ADDRESS, 18, 'USDT', 'Tether')
    const UsdcToken = new Token(31337, USDC_ADDRESS, 18, 'USDC', 'UsdCoin')

    const poolContract1 = new Contract(USDT_USDC_500, artifacts.UniswapV3Pool.abi, provider)
    const poolContract2 = new Contract(USDT_USDC_3000, artifacts.UniswapV3Pool.abi, provider)
    const poolContract3 = new Contract(USDT_USDC_10000, artifacts.UniswapV3Pool.abi, provider)

    const poolData = {}
    poolData[USDT_USDC_500] = await getPoolData(poolContract1)
    poolData[USDT_USDC_3000] = await getPoolData(poolContract2)
    poolData[USDT_USDC_10000] = await getPoolData(poolContract3)

    // appears I cannot interact with contracts in the async map
    const mintParams = {}
    POOL_ADDRESSES.map(async poolAddress => {
    pd = poolData[poolAddress]

    const poolObj = new Pool(
    UsdtToken,
    UsdcToken,
    pd.fee,
    pd.sqrtPriceX96.toString(),
    pd.liquidity.toString(),
    pd.tick
    )

    const tickLower = nearestUsableTick(pd.tick, pd.tickSpacing) - pd.tickSpacing * 100
    const tickUpper = nearestUsableTick(pd.tick, pd.tickSpacing) + pd.tickSpacing * 100

    const positionObj = new Position({
    pool: poolObj,
    liquidity: LIQUIDITY,
    tickLower: tickLower,
    tickUpper: tickUpper,
    })

    const { amount0: amount0Desired, amount1: amount1Desired} = positionObj.mintAmounts
    const params = {
    token0: TETHER_ADDRESS,
    token1: USDC_ADDRESS,
    fee: pd.fee,
    tickLower: tickLower,
    tickUpper: tickUpper,
    amount0Desired: amount0Desired.toString(),
    amount1Desired: amount1Desired.toString(),
    amount0Min: 0,
    amount1Min: 0,
    recipient: signer2.address,
    deadline: DEADLINE
    }

    mintParams[poolAddress] = params
    })

    const tx1 = await nonfungiblePositionManager.connect(owner).mint(mintParams[USDT_USDC_500], { gasLimit: '1000000' })
    await tx1.wait()

    const tx2 = await nonfungiblePositionManager.connect(owner).mint(mintParams[USDT_USDC_3000], { gasLimit: '1000000' })
    await tx2.wait()

    const tx3 = await nonfungiblePositionManager.connect(owner).mint(mintParams[USDT_USDC_10000], { gasLimit: '1000000' })
    await tx3.wait()
    console.log('done')
    }

    /*
    npx hardhat run --network localhost scripts/04_addLiquidity.js
    */

    main()
    .then(() => process.exit(0))
    .catch((error) => {
    console.error(error);
    process.exit(1);
    });
    58 changes: 58 additions & 0 deletions 05_checkLiquidity.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,58 @@
    // pool
    USDT_USDC_500= '0x1FA8DDa81477A5b6FA1b2e149e93ed9C7928992F'
    USDT_USDC_3000= '0x3B00F82071576B8489A6e3df223dcC0e937841d1'
    USDT_USDC_10000= '0xb09EB46A30889ae3cE4AFa5d8ebD136B4f389B85'

    const UniswapV3Pool = require("@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json")
    const { Contract, BigNumber } = require("ethers")

    async function getPoolData(poolContract) {
    const [tickSpacing, fee, liquidity, slot0] = await Promise.all([
    poolContract.tickSpacing(),
    poolContract.fee(),
    poolContract.liquidity(),
    poolContract.slot0(),
    ])

    const sqrtPriceX96 = slot0[0]
    const numerator = BigNumber.from(sqrtPriceX96).pow(2)
    const denominator = BigNumber.from(2).pow(192)

    const priceRatio = numerator/denominator
    return {
    tickSpacing: tickSpacing,
    fee: fee,
    liquidity: liquidity.toString(),
    sqrtPriceX96: sqrtPriceX96.toString(),
    priceRatio: priceRatio.toString(),
    tick: slot0[1],
    }
    }


    async function main() {
    const provider = waffle.provider;

    const poolContract500 = new Contract(USDT_USDC_500, UniswapV3Pool.abi, provider)
    const poolData500 = await getPoolData(poolContract500)
    console.log('poolData500', poolData500)

    const poolContract3000 = new Contract(USDT_USDC_3000, UniswapV3Pool.abi, provider)
    const poolData3000 = await getPoolData(poolContract3000)
    console.log('poolData3000', poolData3000)

    const poolContract10000 = new Contract(USDT_USDC_10000, UniswapV3Pool.abi, provider)
    const poolData10000 = await getPoolData(poolContract10000)
    console.log('poolData10000', poolData10000)
    }

    /*
    npx hardhat run --network localhost scripts/05_checkLiquidity.js
    */

    main()
    .then(() => process.exit(0))
    .catch((error) => {
    console.error(error);
    process.exit(1);
    });
    78 changes: 78 additions & 0 deletions 06_flashSwap.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,78 @@
    const { Contract } = require("ethers")

    // Uniswap contract address
    WETH_ADDRESS= '0x5FbDB2315678afecb367f032d93F642f64180aa3'
    FACTORY_ADDRESS= '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512'
    SWAP_ROUTER_ADDRESS= '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0'
    NFT_DESCRIPTOR_ADDRESS= '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9'
    POSITION_DESCRIPTOR_ADDRESS= '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9'
    POSITION_MANAGER_ADDRESS= '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707'

    // Token addresses
    TETHER_ADDRESS= '0x0165878A594ca255338adfa4d48449f69242Eb8F'
    USDC_ADDRESS= '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853'

    const WETH9 = require("../WETH9.json")
    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"),
    Usdt: require("../artifacts/contracts/Tether.sol/Tether.json"),
    Usdc: require("../artifacts/contracts/UsdCoin.sol/UsdCoin.json"),
    WETH9,
    };

    const toEth = (wei) => ethers.utils.formatEther(wei)

    async function main() {
    const provider = waffle.provider;
    const [owner, signer2] = await ethers.getSigners();

    Flash = await ethers.getContractFactory('PairFlash', signer2);
    flash = await Flash.deploy(SWAP_ROUTER_ADDRESS, FACTORY_ADDRESS, WETH_ADDRESS);
    console.log('flash', flash.address)

    const usdtContract = new Contract(TETHER_ADDRESS,artifacts.Usdt.abi,provider)
    const usdcContract = new Contract(USDC_ADDRESS,artifacts.Usdc.abi,provider)

    let usdtBalance = await usdtContract.connect(provider).balanceOf(signer2.address)
    let usdcBalance = await usdcContract.connect(provider).balanceOf(signer2.address)
    console.log('-------------------- BEFORE')
    console.log('usdtBalance', toEth(usdtBalance.toString()))
    console.log('usdcBalance', toEth(usdcBalance.toString()))
    console.log('--------------------')

    const tx = await flash.connect(signer2).initFlash(
    [
    TETHER_ADDRESS,
    USDC_ADDRESS,
    500,
    ethers.utils.parseEther('1'),
    ethers.utils.parseEther('1'),
    3000,
    10000
    ],
    { gasLimit: ethers.utils.hexlify(1000000) }
    );
    await tx.wait()

    usdtBalance = await usdtContract.connect(provider).balanceOf(signer2.address)
    usdcBalance = await usdcContract.connect(provider).balanceOf(signer2.address)
    console.log('-------------------- AFTER')
    console.log('usdtBalance', toEth(usdtBalance.toString()))
    console.log('usdcBalance', toEth(usdcBalance.toString()))
    console.log('--------------------')
    }

    /*
    npx hardhat run --network localhost scripts/06_flashSwap.js
    */

    main()
    .then(() => process.exit(0))
    .catch((error) => {
    console.error(error);
    process.exit(1);
    });
    136 changes: 136 additions & 0 deletions PairFlash.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,136 @@
    // SPDX-License-Identifier: GPL-2.0-or-later
    pragma solidity =0.7.6;
    pragma abicoder v2;

    import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol';
    import '@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol';

    import '@uniswap/v3-periphery/contracts/base/PeripheryPayments.sol';
    import '@uniswap/v3-periphery/contracts/base/PeripheryImmutableState.sol';
    import '@uniswap/v3-periphery/contracts/libraries/PoolAddress.sol';
    import '@uniswap/v3-periphery/contracts/libraries/CallbackValidation.sol';
    import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
    import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';

    contract PairFlash is IUniswapV3FlashCallback, PeripheryImmutableState, PeripheryPayments {
    using LowGasSafeMath for uint256;
    using LowGasSafeMath for int256;

    ISwapRouter public immutable swapRouter;

    constructor(
    ISwapRouter _swapRouter,
    address _factory,
    address _WETH9
    ) PeripheryImmutableState(_factory, _WETH9) {
    swapRouter = _swapRouter;
    }

    function uniswapV3FlashCallback(
    uint256 fee0,
    uint256 fee1,
    bytes calldata data
    ) external override {
    FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData));
    CallbackValidation.verifyCallback(factory, decoded.poolKey);

    address token0 = decoded.poolKey.token0;
    address token1 = decoded.poolKey.token1;

    TransferHelper.safeApprove(token0, address(swapRouter), decoded.amount0);
    TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1);

    uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1);
    uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0);

    uint256 amountOut0 =
    swapRouter.exactInputSingle(
    ISwapRouter.ExactInputSingleParams({
    tokenIn: token1,
    tokenOut: token0,
    fee: decoded.poolFee2,
    recipient: address(this),
    deadline: block.timestamp,
    amountIn: decoded.amount1,
    amountOutMinimum: amount0Min,
    sqrtPriceLimitX96: 0
    })
    );

    uint256 amountOut1 =
    swapRouter.exactInputSingle(
    ISwapRouter.ExactInputSingleParams({
    tokenIn: token0,
    tokenOut: token1,
    fee: decoded.poolFee3,
    recipient: address(this),
    deadline: block.timestamp,
    amountIn: decoded.amount0,
    amountOutMinimum: amount1Min,
    sqrtPriceLimitX96: 0
    })
    );

    uint256 amount0Owed = LowGasSafeMath.add(decoded.amount0, fee0);
    uint256 amount1Owed = LowGasSafeMath.add(decoded.amount1, fee1);

    TransferHelper.safeApprove(token0, address(this), amount0Owed);
    TransferHelper.safeApprove(token1, address(this), amount1Owed);

    if (amount0Owed > 0) pay(token0, address(this), msg.sender, amount0Owed);
    if (amount1Owed > 0) pay(token1, address(this), msg.sender, amount1Owed);

    if (amountOut0 > amount0Owed) {
    uint256 profit0 = LowGasSafeMath.sub(amountOut0, amount0Owed);

    TransferHelper.safeApprove(token0, address(this), profit0);
    pay(token0, address(this), decoded.payer, profit0);
    }
    if (amountOut1 > amount1Owed) {
    uint256 profit1 = LowGasSafeMath.sub(amountOut1, amount1Owed);
    TransferHelper.safeApprove(token0, address(this), profit1);
    pay(token1, address(this), decoded.payer, profit1);
    }
    }

    struct FlashParams {
    address token0;
    address token1;
    uint24 fee1;
    uint256 amount0;
    uint256 amount1;
    uint24 fee2;
    uint24 fee3;
    }

    struct FlashCallbackData {
    uint256 amount0;
    uint256 amount1;
    address payer;
    PoolAddress.PoolKey poolKey;
    uint24 poolFee2;
    uint24 poolFee3;
    }

    function initFlash(FlashParams memory params) external {
    // {token0: token0, token1: token1, fee: fee}
    PoolAddress.PoolKey memory poolKey = PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee1});
    IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));

    pool.flash(
    address(this),
    params.amount0,
    params.amount1,
    abi.encode(
    FlashCallbackData({
    amount0: params.amount0,
    amount1: params.amount1,
    payer: msg.sender,
    poolKey: poolKey,
    poolFee2: params.fee2,
    poolFee3: params.fee3
    })
    )
    );
    }
    }
    13 changes: 13 additions & 0 deletions Tether.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    // SPDX-License-Identifier: GPL-2.0-or-later
    pragma solidity =0.7.6;

    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);
    }
    }
    13 changes: 13 additions & 0 deletions UsdCoin.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    // SPDX-License-Identifier: GPL-2.0-or-later
    pragma solidity =0.7.6;

    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);
    }
    }
    156 changes: 156 additions & 0 deletions WETH9.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,156 @@
    {
    "bytecode": "60606040526040805190810160405280600d81526020017f57726170706564204574686572000000000000000000000000000000000000008152506000908051906020019061004f9291906100c8565b506040805190810160405280600481526020017f57455448000000000000000000000000000000000000000000000000000000008152506001908051906020019061009b9291906100c8565b506012600260006101000a81548160ff021916908360ff16021790555034156100c357600080fd5b61016d565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061010957805160ff1916838001178555610137565b82800160010185558215610137579182015b8281111561013657825182559160200191906001019061011b565b5b5090506101449190610148565b5090565b61016a91905b8082111561016657600081600090555060010161014e565b5090565b90565b610c348061017c6000396000f3006060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029",
    "abi": [
    {
    "constant": true,
    "inputs": [],
    "name": "name",
    "outputs": [{ "name": "", "type": "string" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
    },
    {
    "constant": false,
    "inputs": [
    { "name": "guy", "type": "address" },
    { "name": "wad", "type": "uint256" }
    ],
    "name": "approve",
    "outputs": [{ "name": "", "type": "bool" }],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
    },
    {
    "constant": true,
    "inputs": [],
    "name": "totalSupply",
    "outputs": [{ "name": "", "type": "uint256" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
    },
    {
    "constant": false,
    "inputs": [
    { "name": "src", "type": "address" },
    { "name": "dst", "type": "address" },
    { "name": "wad", "type": "uint256" }
    ],
    "name": "transferFrom",
    "outputs": [{ "name": "", "type": "bool" }],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
    },
    {
    "constant": false,
    "inputs": [{ "name": "wad", "type": "uint256" }],
    "name": "withdraw",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
    },
    {
    "constant": true,
    "inputs": [],
    "name": "decimals",
    "outputs": [{ "name": "", "type": "uint8" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
    },
    {
    "constant": true,
    "inputs": [{ "name": "", "type": "address" }],
    "name": "balanceOf",
    "outputs": [{ "name": "", "type": "uint256" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
    },
    {
    "constant": true,
    "inputs": [],
    "name": "symbol",
    "outputs": [{ "name": "", "type": "string" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
    },
    {
    "constant": false,
    "inputs": [
    { "name": "dst", "type": "address" },
    { "name": "wad", "type": "uint256" }
    ],
    "name": "transfer",
    "outputs": [{ "name": "", "type": "bool" }],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
    },
    {
    "constant": false,
    "inputs": [],
    "name": "deposit",
    "outputs": [],
    "payable": true,
    "stateMutability": "payable",
    "type": "function"
    },
    {
    "constant": true,
    "inputs": [
    { "name": "", "type": "address" },
    { "name": "", "type": "address" }
    ],
    "name": "allowance",
    "outputs": [{ "name": "", "type": "uint256" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
    },
    { "payable": true, "stateMutability": "payable", "type": "fallback" },
    {
    "anonymous": false,
    "inputs": [
    { "indexed": true, "name": "src", "type": "address" },
    { "indexed": true, "name": "guy", "type": "address" },
    { "indexed": false, "name": "wad", "type": "uint256" }
    ],
    "name": "Approval",
    "type": "event"
    },
    {
    "anonymous": false,
    "inputs": [
    { "indexed": true, "name": "src", "type": "address" },
    { "indexed": true, "name": "dst", "type": "address" },
    { "indexed": false, "name": "wad", "type": "uint256" }
    ],
    "name": "Transfer",
    "type": "event"
    },
    {
    "anonymous": false,
    "inputs": [
    { "indexed": true, "name": "dst", "type": "address" },
    { "indexed": false, "name": "wad", "type": "uint256" }
    ],
    "name": "Deposit",
    "type": "event"
    },
    {
    "anonymous": false,
    "inputs": [
    { "indexed": true, "name": "src", "type": "address" },
    { "indexed": false, "name": "wad", "type": "uint256" }
    ],
    "name": "Withdrawal",
    "type": "event"
    }
    ]
    }
    16 changes: 16 additions & 0 deletions hardhat.config.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    require("@nomiclabs/hardhat-waffle");

    module.exports = {
    solidity: {
    compilers: [
    { version: "0.7.6" },
    ],
    settings: {
    optimizer: {
    enabled: true,
    runs: 5000,
    details: { yul: false },
    },
    }
    },
    };
    23 changes: 23 additions & 0 deletions package.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    {
    "name": "solidity-flashswap-1",
    "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.1",
    "@nomiclabs/hardhat-waffle": "^2.0.3",
    "@openzeppelin/contracts": "^3.4.0",
    "@uniswap/v3-periphery": "^1.0.1",
    "@uniswap/v3-sdk": "^3.9.0",
    "bignumber.js": "^9.1.0",
    "ethereum-waffle": "^3.4.4",
    "ethers": "^5.7.2",
    "hardhat": "^2.12.2"
    }
    }