-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #73 from IndexCoop/feat/add-uniswap-swap-quote-pro…
…vider feat: add uniswap swap quote provider
- Loading branch information
Showing
20 changed files
with
3,563 additions
and
905 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
import { ETH, stETH, WETH } from 'constants/tokens' | ||
import { AlchemyProviderUrl } from 'tests/utils' | ||
|
||
import { IndexSwapQuoteProvider } from './adapter' | ||
import { Exchange } from 'utils' | ||
|
||
const rpcUrl = AlchemyProviderUrl | ||
|
||
// ETH/stETH | ||
const curvePool = '0xdc24316b9ae028f1497c275eb9192a3ea0f67022' | ||
const eth = ETH.address! | ||
const steth = stETH.address! | ||
const weth = WETH.address! | ||
const ONE = '1000000000000000000' | ||
|
||
describe('IndexSwapQuoteProvider', () => { | ||
test('returns a swap quote for buying stETH with ETH', async () => { | ||
const request = { | ||
chainId: 1, | ||
inputToken: eth, | ||
outputToken: steth, | ||
outputAmount: ONE, | ||
} | ||
const provider = new IndexSwapQuoteProvider(rpcUrl) | ||
const quote = await provider.getSwapQuote(request) | ||
if (!quote) fail() | ||
expect(quote).not.toBeNull() | ||
expect(quote.swapData?.exchange).toBe(Exchange.Curve) | ||
expect(quote.swapData?.path.length).toBe(2) | ||
expect(quote.swapData?.fees.length).toBe(0) | ||
expect(quote.swapData?.path).toEqual([weth, steth]) | ||
expect(quote.swapData?.pool).toBe(curvePool) | ||
// expect(quote.callData).not.toBe('0x') | ||
expect(BigInt(quote.inputAmount) > BigInt(0)).toBe(true) | ||
}) | ||
|
||
test('returns a swap quote for selling stETH for ETH', async () => { | ||
const request = { | ||
chainId: 1, | ||
inputToken: steth, | ||
outputToken: eth, | ||
inputAmount: ONE, | ||
} | ||
const provider = new IndexSwapQuoteProvider(rpcUrl) | ||
const quote = await provider.getSwapQuote(request) | ||
if (!quote) fail() | ||
expect(quote).not.toBeNull() | ||
expect(quote.swapData?.exchange).toBe(Exchange.Curve) | ||
expect(quote.swapData?.path.length).toBe(2) | ||
expect(quote.swapData?.fees.length).toBe(0) | ||
expect(quote.swapData?.path).toEqual([weth, steth]) | ||
expect(quote.swapData?.pool).toBe(curvePool) | ||
// expect(quote.callData).not.toBe('0x') | ||
expect(BigInt(quote.inputAmount) > BigInt(0)).toBe(true) | ||
}) | ||
}) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { ETH, stETH } from 'constants/tokens' | ||
import { CurveSwapQuoteProvider } from 'quote/swap/adapters/curve' | ||
import { UniswapSwapQuoteProvider } from 'quote/swap/adapters/uniswap' | ||
import { | ||
SwapQuote, | ||
SwapQuoteProvider, | ||
SwapQuoteRequest, | ||
} from 'quote/swap/interfaces' | ||
import { isSameAddress } from 'utils' | ||
|
||
export class IndexSwapQuoteProvider implements SwapQuoteProvider { | ||
constructor(readonly rpcUrl: string) {} | ||
|
||
public async getSwapQuote( | ||
request: SwapQuoteRequest | ||
): Promise<SwapQuote | null> { | ||
let inputToken = request.inputToken | ||
let outputToken = request.outputToken | ||
if (inputToken === 'ETH') { | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
inputToken = ETH.address! | ||
} | ||
if (outputToken === 'ETH') { | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
outputToken = ETH.address! | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const eth = ETH.address! | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const stEth = stETH.address! | ||
const isEth = | ||
isSameAddress(inputToken, eth) || isSameAddress(outputToken, eth) | ||
const isStEth = | ||
isSameAddress(inputToken, stEth) || isSameAddress(outputToken, stEth) | ||
if (isStEth && isEth) { | ||
const curveSwapQuoteProvider = new CurveSwapQuoteProvider(this.rpcUrl) | ||
return await curveSwapQuoteProvider.getSwapQuote({ | ||
...request, | ||
inputToken, | ||
outputToken, | ||
}) | ||
} | ||
const uniswapSwapQuoteProvider = new UniswapSwapQuoteProvider(this.rpcUrl) | ||
return await uniswapSwapQuoteProvider.getSwapQuote({ | ||
...request, | ||
inputToken, | ||
outputToken, | ||
}) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
import { ETH, stETH, WETH } from 'constants/tokens' | ||
import { AlchemyProviderUrl } from 'tests/utils' | ||
|
||
import { CurveSwapQuoteProvider } from './' | ||
import { Exchange } from 'utils' | ||
|
||
const rpcUrl = AlchemyProviderUrl | ||
|
||
// ETH/stETH | ||
const curvePool = '0xdc24316b9ae028f1497c275eb9192a3ea0f67022' | ||
const eth = ETH.address! | ||
const steth = stETH.address! | ||
const weth = WETH.address! | ||
const ONE = '1000000000000000000' | ||
|
||
describe('CurveSwapQuoteProvider', () => { | ||
test('getting a swap quote for buying stETH', async () => { | ||
const request = { | ||
chainId: 1, | ||
inputToken: eth, | ||
outputToken: steth, | ||
outputAmount: ONE, | ||
} | ||
const provider = new CurveSwapQuoteProvider(rpcUrl) | ||
const quote = await provider.getSwapQuote(request) | ||
if (!quote) fail() | ||
expect(quote).not.toBeNull() | ||
expect(quote.swapData?.exchange).toBe(Exchange.Curve) | ||
expect(quote.swapData?.path.length).toBe(2) | ||
expect(quote.swapData?.fees.length).toBe(0) | ||
expect(quote.swapData?.path).toEqual([weth, steth]) | ||
expect(quote.swapData?.pool).toBe(curvePool) | ||
// expect(quote.callData).not.toBe('0x') | ||
expect(BigInt(quote.inputAmount) > BigInt(0)).toBe(true) | ||
}) | ||
|
||
test('getting a swap quote for selling stETH', async () => { | ||
const request = { | ||
chainId: 1, | ||
inputToken: steth, | ||
outputToken: eth, | ||
inputAmount: ONE, | ||
} | ||
const provider = new CurveSwapQuoteProvider(rpcUrl) | ||
const quote = await provider.getSwapQuote(request) | ||
if (!quote) fail() | ||
expect(quote).not.toBeNull() | ||
expect(quote.swapData?.exchange).toBe(Exchange.Curve) | ||
expect(quote.swapData?.path.length).toBe(2) | ||
expect(quote.swapData?.fees.length).toBe(0) | ||
expect(quote.swapData?.path).toEqual([weth, steth]) | ||
expect(quote.swapData?.pool).toBe(curvePool) | ||
// expect(quote.callData).not.toBe('0x') | ||
expect(BigInt(quote.inputAmount) > BigInt(0)).toBe(true) | ||
}) | ||
}) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { BigNumber } from '@ethersproject/bignumber' | ||
import { Contract } from '@ethersproject/contracts' | ||
|
||
import { | ||
SwapQuoteProvider, | ||
SwapQuoteRequest, | ||
SwapQuote, | ||
} from 'quote/swap/interfaces' | ||
import { getRpcProvider } from 'utils/rpc-provider' | ||
|
||
import { getSwapData } from './swap-data' | ||
|
||
export class CurveSwapQuoteProvider implements SwapQuoteProvider { | ||
constructor(readonly rpcUrl: string) {} | ||
|
||
getPoolContract() { | ||
const rpcProvider = getRpcProvider(this.rpcUrl) | ||
const pool = '0xdc24316b9ae028f1497c275eb9192a3ea0f67022' | ||
const abi = [ | ||
'function get_dy(int128 i, int128 j, uint256 dx) public view returns (uint256)', | ||
] | ||
return new Contract(pool, abi, rpcProvider) | ||
} | ||
|
||
async getSwapQuote(request: SwapQuoteRequest): Promise<SwapQuote | null> { | ||
const { | ||
chainId, | ||
inputAmount, | ||
inputToken, | ||
outputAmount, | ||
outputToken, | ||
slippage, | ||
} = request | ||
const pool = this.getPoolContract() | ||
let quoteAmount = BigNumber.from(0) | ||
if (outputAmount) { | ||
quoteAmount = await pool.get_dy(0, 1, BigNumber.from(outputAmount)) | ||
} else { | ||
quoteAmount = await pool.get_dy(1, 0, BigNumber.from(inputAmount)) | ||
} | ||
return { | ||
chainId, | ||
inputToken, | ||
outputToken, | ||
inputAmount: inputAmount ?? quoteAmount.toString(), | ||
outputAmount: outputAmount ?? quoteAmount.toString(), | ||
// Will not be used anywhere, so no need to return constructed call data | ||
callData: '0x', | ||
slippage: slippage ?? 0, | ||
swapData: getSwapData(), | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { stETH, WETH } from 'constants/tokens' | ||
import { Exchange, SwapData } from 'utils' | ||
|
||
export function getSwapData(): SwapData { | ||
// The curve adapter is mostly just used for ETH/stETH swapping, so we can | ||
// hard-code the return here. | ||
return { | ||
exchange: Exchange.Curve, | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
path: [WETH.address!, stETH.address!], | ||
fees: [], // not needed for curve | ||
pool: '0xdc24316b9ae028f1497c275eb9192a3ea0f67022', | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './adapter' | ||
export * from './zeroex' |
Oops, something went wrong.