Skip to content

Commit

Permalink
feat: add first iteration of returning a quote
Browse files Browse the repository at this point in the history
  • Loading branch information
janndriessen committed Jun 28, 2024
1 parent ab49768 commit 541d8f0
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 46 deletions.
40 changes: 26 additions & 14 deletions src/quote/swap/adapters/uniswap/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,58 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import 'dotenv/config'

import { EthAddress } from 'constants/addresses'
import { WETH } from 'constants/tokens'
import { AlchemyProviderUrl } from 'tests/utils'

import { UniswapSwapQuoteProvider } from './'
import { Exchange } from 'utils'

const rpcUrl = process.env.MAINNET_ALCHEMY_API!
const rpcUrl = AlchemyProviderUrl

const ETH = EthAddress
const weth = WETH.address!
const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
const ONE = '1000000000000000000'

describe('UniswapSwapQuoteProvider', () => {
test.skip('getting a swap quote for a specified output amount', async () => {
test('getting a swap quote for a specified output amount', async () => {
const request = {
address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
chainId: 1,
inputToken: ETH,
inputToken: weth,
outputToken: USDC,
outputAmount: '1000000',
outputAmount: '100000000',
}
const provider = new UniswapSwapQuoteProvider(rpcUrl)
const quote = await provider.getSwapQuote(request)
if (!quote) fail()
expect(quote).not.toBeNull()
expect(quote.callData).not.toBe('0x')
expect(quote.swapData?.exchange).toBe(Exchange.UniV3)
expect(quote.swapData?.path.length).toBe(2)
expect(quote.swapData?.fees.length).toBe(1)
expect(quote.swapData?.path).toEqual([
request.inputToken,
request.outputToken,
])
// expect(quote.callData).not.toBe('0x')
expect(quote.inputAmount).not.toBeNull()
})

test('getting a swap quote for a specified input amount', async () => {
const request = {
address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
chainId: 1,
inputToken: ETH,
inputToken: weth,
outputToken: USDC,
inputAmount: ONE,
}
const provider = new UniswapSwapQuoteProvider(rpcUrl)
const quote = await provider.getSwapQuote(request)
if (!quote) fail()
expect(quote).not.toBeNull()
expect(quote.callData).not.toBe('0x')
expect(quote.inputAmount).not.toBeNull()
expect(quote.swapData?.exchange).toBe(Exchange.UniV3)
expect(quote.swapData?.path.length).toBe(2)
expect(quote.swapData?.fees.length).toBe(1)
expect(quote.swapData?.path).toEqual([
request.inputToken,
request.outputToken,
])
// expect(quote.callData).not.toBe('0x')
expect(quote.outputAmount).not.toBeNull()
})
})
121 changes: 89 additions & 32 deletions src/quote/swap/adapters/uniswap/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { BigNumber } from '@ethersproject/bignumber'
import { Contract } from '@ethersproject/contracts'
import Quoter from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json'
import { Token } from '@uniswap/sdk-core'

import {
SwapQuote,
SwapQuoteProvider,
SwapQuoteRequest,
} from 'quote/swap/interfaces'
import { getRpcProvider } from 'utils/rpc-provider'
import { Exchange, isSameAddress } from 'utils'

import { getPool } from './utils/pools'

const QUOTER_CONTRACT_ADDRESS = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'

export class UniswapSwapQuoteProvider implements SwapQuoteProvider {
constructor(readonly rpcUrl: string) {}

getQuoterContract() {
const rpcProvider = getRpcProvider(this.rpcUrl)
return new Contract(QUOTER_CONTRACT_ADDRESS, Quoter.abi, rpcProvider)
}

async getSwapQuote(request: SwapQuoteRequest): Promise<SwapQuote | null> {
const {
chainId,
Expand All @@ -23,46 +32,94 @@ export class UniswapSwapQuoteProvider implements SwapQuoteProvider {
outputToken,
slippage,
} = request

// TODO: if input/output token is ETH, change to WETH

if (!inputAmount && !outputAmount) {
throw new Error('Error - either input or output amount must be set')
}

// TODO: just for testing, remove later
const isStEth =
isSameAddress(
outputToken,
'0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84'
) ||
isSameAddress(inputToken, '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84')

const isInputTokenUsdc = isSameAddress(
inputToken,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
)
const isOutputTokenUsdc = isSameAddress(
outputToken,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
)
const isInputTokenUsdt = isSameAddress(
inputToken,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
)
const isOutputTokenUsdt = isSameAddress(
outputToken,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
)

// TODO: get decimals from tokenlists
const decimalsInputToken = isInputTokenUsdc || isInputTokenUsdt ? 6 : 18
const decimalsOutputToken = isOutputTokenUsdc || isOutputTokenUsdt ? 6 : 18

try {
const rpcProvider = getRpcProvider(this.rpcUrl)
const quoterContract = new Contract(
QUOTER_CONTRACT_ADDRESS,
Quoter.abi,
rpcProvider
)
// const poolConstants = await getPoolConstants()
console.log(inputToken)
console.log(outputToken)
console.log(BigNumber.from(inputAmount).toString())
const quotedAmountOut =
await quoterContract.callStatic.quoteExactInputSingle(
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
outputToken,
500,
// TODO: decimals? (probably not needed)
BigNumber.from(inputAmount),
// TODO: create convenience function to fetch best pool (best fees)?
const fee = isStEth ? 3000 : 500
const tokenA = new Token(1, inputToken, decimalsInputToken)
const tokenB = new Token(1, outputToken, decimalsOutputToken)
const pool = await getPool(tokenA, tokenB, fee, this.rpcUrl)
if (!pool) return null
const quoterContract = this.getQuoterContract()
let quotedAmount: BigNumber | null
if (outputAmount) {
quotedAmount = await quoterContract.callStatic.quoteExactInputSingle(
pool.token0,
pool.token1,
pool.fee,
BigNumber.from(outputAmount),
0
)
} else {
quotedAmount = await quoterContract.callStatic.quoteExactOutputSingle(
pool.token0,
pool.token1,
pool.fee,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
BigNumber.from(inputAmount!),
0
)
console.log(quotedAmountOut.toString())
// TODO: fetch pool
// TODO: create convenience function to fetch best pool (best fees)
// TODO: get quote
}
// TODO: construct swap data
// const swapData = getSwapData(result)
// return {
// chainId,
// inputToken,
// outputToken,
// inputAmount: estimate.fromAmount,
// outputAmount: estimate.toAmount,
// callData: result.transactionRequest?.data ?? '0x',
// slippage: slippage ?? 0,
// swapData,
// }
return null
return {
chainId,
inputToken,
outputToken,
inputAmount: (
inputAmount ??
quotedAmount ??
BigNumber.from(0)
)?.toString(),
outputAmount: (
outputAmount ??
quotedAmount ??
BigNumber.from(0)
)?.toString(),
callData: '0x', // TOOD: result.transactionRequest?.data ?? '0x',
slippage: slippage ?? 0,
swapData: {
exchange: Exchange.UniV3,
path: [inputToken, outputToken],
fees: [pool.fee],
pool: pool.address,
},
}
} catch (error) {
console.log('Error getting Uniswap swap quote:')
console.log(error)
Expand Down

0 comments on commit 541d8f0

Please sign in to comment.