import { ethers } from "ethers" ; import { BigNumber } from "ethers/utils" ; import * as UniswapExchangeArtifact from "@joincivil/artifacts/v1/UniswapExchange.json" ; import * as UniswapFactoryArtifact from "@joincivil/artifacts/v1/UniswapFactory.json" ; import * as CVLTokenArtifact from "@joincivil/artifacts/v1/CVLToken.json" ; export class UniswapService { private signer : ethers . Signer | undefined ; private networkID : number ; private factory : ethers . Contract ; private exchange : ethers . Contract ; private token : ethers . Contract ; private provider : ethers . providers . Provider ; public constructor ( provider , signer , networkID ) { this . provider = provider ; this . networkID = networkID ; if ( ! ( CVLTokenArtifact as any ) . networks [ this . networkID ] ) { throw new Error ( "unsupported network: " + this . networkID ) ; } const exchangeAddress = ( UniswapExchangeArtifact as any ) . networks [ this . networkID ] . address ; const cvlAddress = ( CVLTokenArtifact as any ) . networks [ this . networkID ] . address ; const factoryAddress = ( UniswapFactoryArtifact as any ) . networks [ this . networkID ] . address ; this . factory = new ethers . Contract ( factoryAddress , ( UniswapFactoryArtifact as any ) . abi , provider ) ; this . exchange = new ethers . Contract ( exchangeAddress , ( UniswapExchangeArtifact as any ) . abi , provider ) ; this . token = new ethers . Contract ( cvlAddress , ( CVLTokenArtifact as any ) . abi , provider ) ; this . signer = signer ; if ( signer ) { this . factory = this . factory . connect ( signer ) ; this . exchange = this . exchange . connect ( signer ) ; this . token = this . token . connect ( signer ) ; } } public createExchange ( ) { const cvlAddr = ( CVLTokenArtifact as any ) . networks [ this . networkID ] . address ; return this . factory . createExchange ( cvlAddr ) ; } public async priceToBuyCVL ( tokensToBuy ) { const outputAmount = tokensToBuy ; const inputReserve = await this . provider . getBalance ( this . exchange . address ) ; const outputReserve = await this . token . balanceOf ( this . exchange . address ) ; const numerator = outputAmount . mul ( inputReserve ) . mul ( 1000 ) ; const denominator = outputReserve . sub ( outputAmount ) . mul ( 997 ) ; const etherToPay = numerator . div ( denominator ) . add ( 1 ) ; return etherToPay ; } public async quoteETHToCVL ( etherToSpend ) { const inputAmount = etherToSpend ; const inputReserve = await this . provider . getBalance ( this . exchange . address ) ; const outputReserve = await this . token . balanceOf ( this . exchange . address ) ; const numerator = inputAmount . mul ( outputReserve ) . mul ( 997 ) ; const denominator = inputReserve . mul ( 1000 ) . add ( inputAmount . mul ( 997 ) ) ; const cvlToReceive = numerator . div ( denominator ) ; return cvlToReceive ; } public async quoteCVLToETH ( cvlToSell ) { const inputAmount = cvlToSell ; const inputReserve = await this . token . balanceOf ( this . exchange . address ) ; const outputReserve = await this . provider . getBalance ( this . exchange . address ) ; const numerator = inputAmount . mul ( outputReserve ) . mul ( 997 ) ; const denominator = inputReserve . mul ( 1000 ) . add ( inputAmount . mul ( 997 ) ) ; const ethToReceive = numerator . div ( denominator ) ; return ethToReceive ; } public async approvedSellAmountForAddress ( address ) { return this . token . allowance ( address , this . exchange . address ) ; } public async getApprovedSellAmount ( ) { if ( ! this . signer ) { throw new Error ( "no signer on UniswapService" ) ; } const address = await this . signer . getAddress ( ) ; return this . token . allowance ( address , this . exchange . address ) ; } public async setApprovedSellAmount ( amount ) { return this . token . approve ( this . exchange . address , amount ) ; } public async executeETHToCVL ( ethToSpend , minTokensToReceive ) { console . log ( "executeETHToCVL" , ethers . utils . formatEther ( ethToSpend ) , ethers . utils . formatEther ( minTokensToReceive ) ) ; const deadline = await this . getBlockDeadline ( ) ; console . log ( "block deadline: " , deadline ) ; const overrides = { value : ethToSpend , gasLimit : 100000 , } ; return this . exchange . ethToTokenSwapInput ( minTokensToReceive , deadline , overrides ) ; } public async executeCVLToETH ( cvlToSpend , minETHToReceive ) { const deadline = await this . getBlockDeadline ( ) ; console . log ( "executeCVLToETH" , ethers . utils . formatEther ( cvlToSpend ) , minETHToReceive . toString ( ) , deadline . toString ( ) ) ; const overrides = { gasLimit : 100000 , } ; return this . exchange . tokenToEthSwapInput ( cvlToSpend , minETHToReceive , deadline , overrides ) ; } public parseEther ( amount ) { return ethers . utils . parseEther ( amount ) ; } public weiToEtherNumber ( amount ) { const etherAmountString = ethers . utils . formatEther ( amount ) ; const etherAmountNumber = Number . parseFloat ( etherAmountString ) ; return Math . round ( etherAmountNumber * 1000 ) / 1000 ; } private async getBlockDeadline ( ) { const deadline = 600 ; const blockNumber = await this . provider . getBlockNumber ( ) ; if ( ! blockNumber && blockNumber !== 0 ) { throw new Error ( "invalid block number" ) ; } const block = await this . provider . getBlock ( blockNumber ) ; if ( ! block ) { throw new Error ( "invalid block" ) ; } return block . timestamp + deadline ; } }