Commit 280d3be3 authored by Moody Salem's avatar Moody Salem Committed by GitHub

make the fetching code more tree shakeable (#27)

* make the fetching code more tree shakeable

* more documentation everywhere
parent f10af5d0
......@@ -13,8 +13,17 @@ export class Currency {
public readonly symbol?: string
public readonly name?: string
/**
* The only instance of the base class `Currency`.
*/
public static readonly ETHER: Currency = new Currency(18, 'ETH', 'Ether')
/**
* Constructs an instance of the base class `Currency`. The only instance of the base class `Currency` is `Currency.ETHER`.
* @param decimals decimals of the currency
* @param symbol symbol of the currency
* @param name of the currency
*/
protected constructor(decimals: number, symbol?: string, name?: string) {
validateSolidityTypeInstance(JSBI.BigInt(decimals), SolidityType.uint8)
......
......@@ -31,25 +31,29 @@ export class CurrencyAmount extends Fraction {
this.currency = currency
}
get raw(): JSBI {
public get raw(): JSBI {
return this.numerator
}
add(other: CurrencyAmount): CurrencyAmount {
public add(other: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(this.currency, other.currency), 'TOKEN')
return new CurrencyAmount(this.currency, JSBI.add(this.raw, other.raw))
}
subtract(other: CurrencyAmount): CurrencyAmount {
public subtract(other: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(this.currency, other.currency), 'TOKEN')
return new CurrencyAmount(this.currency, JSBI.subtract(this.raw, other.raw))
}
toSignificant(significantDigits: number = 6, format?: object, rounding: Rounding = Rounding.ROUND_DOWN): string {
public toSignificant(
significantDigits: number = 6,
format?: object,
rounding: Rounding = Rounding.ROUND_DOWN
): string {
return super.toSignificant(significantDigits, format, rounding)
}
toFixed(
public toFixed(
decimalPlaces: number = this.currency.decimals,
format?: object,
rounding: Rounding = Rounding.ROUND_DOWN
......@@ -58,7 +62,7 @@ export class CurrencyAmount extends Fraction {
return super.toFixed(decimalPlaces, format, rounding)
}
toExact(format: object = { groupSeparator: '' }): string {
public toExact(format: object = { groupSeparator: '' }): string {
Big.DP = this.currency.decimals
return new Big(this.numerator.toString()).div(this.denominator.toString()).toFormat(format)
}
......
......@@ -27,26 +27,26 @@ export class Fraction {
public readonly numerator: JSBI
public readonly denominator: JSBI
constructor(numerator: BigintIsh, denominator: BigintIsh = ONE) {
public constructor(numerator: BigintIsh, denominator: BigintIsh = ONE) {
this.numerator = parseBigintIsh(numerator)
this.denominator = parseBigintIsh(denominator)
}
// performs floor division
get quotient(): JSBI {
public get quotient(): JSBI {
return JSBI.divide(this.numerator, this.denominator)
}
// remainder after floor division
get remainder(): Fraction {
public get remainder(): Fraction {
return new Fraction(JSBI.remainder(this.numerator, this.denominator), this.denominator)
}
invert(): Fraction {
public invert(): Fraction {
return new Fraction(this.denominator, this.numerator)
}
add(other: Fraction | BigintIsh): Fraction {
public add(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
if (JSBI.equal(this.denominator, otherParsed.denominator)) {
return new Fraction(JSBI.add(this.numerator, otherParsed.numerator), this.denominator)
......@@ -60,7 +60,7 @@ export class Fraction {
)
}
subtract(other: Fraction | BigintIsh): Fraction {
public subtract(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
if (JSBI.equal(this.denominator, otherParsed.denominator)) {
return new Fraction(JSBI.subtract(this.numerator, otherParsed.numerator), this.denominator)
......@@ -74,7 +74,7 @@ export class Fraction {
)
}
lessThan(other: Fraction | BigintIsh): boolean {
public lessThan(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.lessThan(
JSBI.multiply(this.numerator, otherParsed.denominator),
......@@ -82,7 +82,7 @@ export class Fraction {
)
}
equalTo(other: Fraction | BigintIsh): boolean {
public equalTo(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.equal(
JSBI.multiply(this.numerator, otherParsed.denominator),
......@@ -90,7 +90,7 @@ export class Fraction {
)
}
greaterThan(other: Fraction | BigintIsh): boolean {
public greaterThan(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.greaterThan(
JSBI.multiply(this.numerator, otherParsed.denominator),
......@@ -98,7 +98,7 @@ export class Fraction {
)
}
multiply(other: Fraction | BigintIsh): Fraction {
public multiply(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return new Fraction(
JSBI.multiply(this.numerator, otherParsed.numerator),
......@@ -106,7 +106,7 @@ export class Fraction {
)
}
divide(other: Fraction | BigintIsh): Fraction {
public divide(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return new Fraction(
JSBI.multiply(this.numerator, otherParsed.denominator),
......@@ -114,7 +114,7 @@ export class Fraction {
)
}
toSignificant(
public toSignificant(
significantDigits: number,
format: object = { groupSeparator: '' },
rounding: Rounding = Rounding.ROUND_HALF_UP
......@@ -129,7 +129,7 @@ export class Fraction {
return quotient.toFormat(quotient.decimalPlaces(), format)
}
toFixed(
public toFixed(
decimalPlaces: number,
format: object = { groupSeparator: '' },
rounding: Rounding = Rounding.ROUND_HALF_UP
......
......@@ -4,11 +4,11 @@ import { Fraction } from './fraction'
const _100_PERCENT = new Fraction(_100)
export class Percent extends Fraction {
toSignificant(significantDigits: number = 5, format?: object, rounding?: Rounding): string {
public toSignificant(significantDigits: number = 5, format?: object, rounding?: Rounding): string {
return this.multiply(_100_PERCENT).toSignificant(significantDigits, format, rounding)
}
toFixed(decimalPlaces: number = 2, format?: object, rounding?: Rounding): string {
public toFixed(decimalPlaces: number = 2, format?: object, rounding?: Rounding): string {
return this.multiply(_100_PERCENT).toFixed(decimalPlaces, format, rounding)
}
}
......@@ -15,7 +15,7 @@ export class Price extends Fraction {
public readonly quoteCurrency: Currency // output i.e. numerator
public readonly scalar: Fraction // used to adjust the raw fraction w/r/t the decimals of the {base,quote}Token
static fromRoute(route: Route): Price {
public static fromRoute(route: Route): Price {
const prices: Price[] = []
for (const [i, pair] of route.pairs.entries()) {
prices.push(
......@@ -28,7 +28,7 @@ export class Price extends Fraction {
}
// denominator and numerator _must_ be raw, i.e. in the native representation
constructor(baseCurrency: Currency, quoteCurrency: Currency, denominator: BigintIsh, numerator: BigintIsh) {
public constructor(baseCurrency: Currency, quoteCurrency: Currency, denominator: BigintIsh, numerator: BigintIsh) {
super(numerator, denominator)
this.baseCurrency = baseCurrency
......@@ -39,26 +39,26 @@ export class Price extends Fraction {
)
}
get raw(): Fraction {
public get raw(): Fraction {
return new Fraction(this.numerator, this.denominator)
}
get adjusted(): Fraction {
public get adjusted(): Fraction {
return super.multiply(this.scalar)
}
invert(): Price {
public invert(): Price {
return new Price(this.quoteCurrency, this.baseCurrency, this.numerator, this.denominator)
}
multiply(other: Price): Price {
public multiply(other: Price): Price {
invariant(currencyEquals(this.quoteCurrency, other.baseCurrency), 'TOKEN')
const fraction = super.multiply(other)
return new Price(this.baseCurrency, other.quoteCurrency, fraction.denominator, fraction.numerator)
}
// performs floor division on overflow
quote(currencyAmount: CurrencyAmount): CurrencyAmount {
public quote(currencyAmount: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(currencyAmount.currency, this.baseCurrency), 'TOKEN')
if (this.quoteCurrency instanceof Token) {
return new TokenAmount(this.quoteCurrency, super.multiply(currencyAmount.raw).quotient)
......@@ -66,11 +66,11 @@ export class Price extends Fraction {
return CurrencyAmount.ether(super.multiply(currencyAmount.raw).quotient)
}
toSignificant(significantDigits: number = 6, format?: object, rounding?: Rounding): string {
public toSignificant(significantDigits: number = 6, format?: object, rounding?: Rounding): string {
return this.adjusted.toSignificant(significantDigits, format, rounding)
}
toFixed(decimalPlaces: number = 4, format?: object, rounding?: Rounding): string {
public toFixed(decimalPlaces: number = 4, format?: object, rounding?: Rounding): string {
return this.adjusted.toFixed(decimalPlaces, format, rounding)
}
}
......@@ -9,17 +9,17 @@ export class TokenAmount extends CurrencyAmount {
public readonly token: Token
// amount _must_ be raw, i.e. in the native representation
constructor(token: Token, amount: BigintIsh) {
public constructor(token: Token, amount: BigintIsh) {
super(token, amount)
this.token = token
}
add(other: TokenAmount): TokenAmount {
public add(other: TokenAmount): TokenAmount {
invariant(this.token.equals(other.token), 'TOKEN')
return new TokenAmount(this.token, JSBI.add(this.raw, other.raw))
}
subtract(other: TokenAmount): TokenAmount {
public subtract(other: TokenAmount): TokenAmount {
invariant(this.token.equals(other.token), 'TOKEN')
return new TokenAmount(this.token, JSBI.subtract(this.raw, other.raw))
}
......
import { TokenAmount } from './fractions/tokenAmount'
import invariant from 'tiny-invariant'
import JSBI from 'jsbi'
import { getNetwork } from '@ethersproject/networks'
import { getDefaultProvider } from '@ethersproject/providers'
import { Contract } from '@ethersproject/contracts'
import { pack, keccak256 } from '@ethersproject/solidity'
import { getCreate2Address } from '@ethersproject/address'
......@@ -19,25 +16,24 @@ import {
_1000,
ChainId
} from '../constants'
import IUniswapV2Pair from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { sqrt, parseBigintIsh } from '../utils'
import { InsufficientReservesError, InsufficientInputAmountError } from '../errors'
import { Token } from './token'
let CACHE: { [token0Address: string]: { [token1Address: string]: string } } = {}
let PAIR_ADDRESS_CACHE: { [token0Address: string]: { [token1Address: string]: string } } = {}
export class Pair {
public readonly liquidityToken: Token
private readonly tokenAmounts: [TokenAmount, TokenAmount]
static getAddress(tokenA: Token, tokenB: Token): string {
public static getAddress(tokenA: Token, tokenB: Token): string {
const tokens = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] // does safety checks
if (CACHE?.[tokens[0].address]?.[tokens[1].address] === undefined) {
CACHE = {
...CACHE,
if (PAIR_ADDRESS_CACHE?.[tokens[0].address]?.[tokens[1].address] === undefined) {
PAIR_ADDRESS_CACHE = {
...PAIR_ADDRESS_CACHE,
[tokens[0].address]: {
...CACHE?.[tokens[0].address],
...PAIR_ADDRESS_CACHE?.[tokens[0].address],
[tokens[1].address]: getCreate2Address(
FACTORY_ADDRESS,
keccak256(['bytes'], [pack(['address', 'address'], [tokens[0].address, tokens[1].address])]),
......@@ -47,22 +43,10 @@ export class Pair {
}
}
return CACHE[tokens[0].address][tokens[1].address]
return PAIR_ADDRESS_CACHE[tokens[0].address][tokens[1].address]
}
static async fetchData(
tokenA: Token,
tokenB: Token,
provider = getDefaultProvider(getNetwork(tokenA.chainId))
): Promise<Pair> {
invariant(tokenA.chainId === tokenB.chainId, 'CHAIN_ID')
const address = Pair.getAddress(tokenA, tokenB)
const [reserves0, reserves1] = await new Contract(address, IUniswapV2Pair.abi, provider).getReserves()
const balances = tokenA.sortsBefore(tokenB) ? [reserves0, reserves1] : [reserves1, reserves0]
return new Pair(new TokenAmount(tokenA, balances[0]), new TokenAmount(tokenB, balances[1]))
}
constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount) {
public constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount) {
const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks
? [tokenAmountA, tokenAmountB]
: [tokenAmountB, tokenAmountA]
......@@ -88,28 +72,28 @@ export class Pair {
return this.token0.chainId
}
get token0(): Token {
public get token0(): Token {
return this.tokenAmounts[0].token
}
get token1(): Token {
public get token1(): Token {
return this.tokenAmounts[1].token
}
get reserve0(): TokenAmount {
public get reserve0(): TokenAmount {
return this.tokenAmounts[0]
}
get reserve1(): TokenAmount {
public get reserve1(): TokenAmount {
return this.tokenAmounts[1]
}
reserveOf(token: Token): TokenAmount {
public reserveOf(token: Token): TokenAmount {
invariant(this.involvesToken(token), 'TOKEN')
return token.equals(this.token0) ? this.reserve0 : this.reserve1
}
getOutputAmount(inputAmount: TokenAmount): [TokenAmount, Pair] {
public getOutputAmount(inputAmount: TokenAmount): [TokenAmount, Pair] {
invariant(this.involvesToken(inputAmount.token), 'TOKEN')
if (JSBI.equal(this.reserve0.raw, ZERO) || JSBI.equal(this.reserve1.raw, ZERO)) {
throw new InsufficientReservesError()
......@@ -129,7 +113,7 @@ export class Pair {
return [outputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}
getInputAmount(outputAmount: TokenAmount): [TokenAmount, Pair] {
public getInputAmount(outputAmount: TokenAmount): [TokenAmount, Pair] {
invariant(this.involvesToken(outputAmount.token), 'TOKEN')
if (
JSBI.equal(this.reserve0.raw, ZERO) ||
......@@ -150,7 +134,11 @@ export class Pair {
return [inputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}
getLiquidityMinted(totalSupply: TokenAmount, tokenAmountA: TokenAmount, tokenAmountB: TokenAmount): TokenAmount {
public getLiquidityMinted(
totalSupply: TokenAmount,
tokenAmountA: TokenAmount,
tokenAmountB: TokenAmount
): TokenAmount {
invariant(totalSupply.token.equals(this.liquidityToken), 'LIQUIDITY')
const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks
? [tokenAmountA, tokenAmountB]
......@@ -171,7 +159,7 @@ export class Pair {
return new TokenAmount(this.liquidityToken, liquidity)
}
getLiquidityValue(
public getLiquidityValue(
token: Token,
totalSupply: TokenAmount,
liquidity: TokenAmount,
......
......@@ -13,7 +13,7 @@ export class Route {
public readonly output: Currency
public readonly midPrice: Price
constructor(pairs: Pair[], input: Currency, output?: Currency) {
public constructor(pairs: Pair[], input: Currency, output?: Currency) {
invariant(pairs.length > 0, 'PAIRS')
invariant(
pairs.every(pair => pair.chainId === pairs[0].chainId),
......@@ -46,7 +46,7 @@ export class Route {
this.output = output ?? path[path.length - 1]
}
get chainId(): ChainId {
public get chainId(): ChainId {
return this.pairs[0].chainId
}
}
import { Currency } from './currency'
import invariant from 'tiny-invariant'
import { getNetwork } from '@ethersproject/networks'
import { getDefaultProvider } from '@ethersproject/providers'
import { Contract } from '@ethersproject/contracts'
import { ChainId } from '../constants'
import ERC20 from '../abis/ERC20.json'
import { validateAndParseAddress } from '../utils'
let CACHE: { [chainId: number]: { [address: string]: number } } = {
[ChainId.MAINNET]: {
'0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A': 9 // DGD
}
}
/**
* Represents an ERC20 token with a unique address and some metadata.
*/
......@@ -21,36 +10,13 @@ export class Token extends Currency {
public readonly chainId: ChainId
public readonly address: string
static async fetchData(
chainId: ChainId,
address: string,
provider = getDefaultProvider(getNetwork(chainId)),
symbol?: string,
name?: string
): Promise<Token> {
const parsedDecimals =
typeof CACHE?.[chainId]?.[address] === 'number'
? CACHE[chainId][address]
: await new Contract(address, ERC20, provider).decimals().then((decimals: number): number => {
CACHE = {
...CACHE,
[chainId]: {
...CACHE?.[chainId],
[address]: decimals
}
}
return decimals
})
return new Token(chainId, address, parsedDecimals, symbol, name)
}
constructor(chainId: ChainId, address: string, decimals: number, symbol?: string, name?: string) {
public constructor(chainId: ChainId, address: string, decimals: number, symbol?: string, name?: string) {
super(decimals, symbol, name)
this.chainId = chainId
this.address = validateAndParseAddress(address)
}
equals(other: Token): boolean {
public equals(other: Token): boolean {
// short circuit on reference equality
if (this === other) {
return true
......@@ -65,7 +31,7 @@ export class Token extends Currency {
return equivalent
}
sortsBefore(other: Token): boolean {
public sortsBefore(other: Token): boolean {
invariant(this.chainId === other.chainId, 'CHAIN_IDS')
invariant(this.address !== other.address, 'ADDRESSES')
return this.address.toLowerCase() < other.address.toLowerCase()
......
......@@ -177,7 +177,10 @@ export class Trade {
this.priceImpact = computePriceImpact(route.midPrice, this.inputAmount, this.outputAmount)
}
// get the minimum amount that must be received from this trade for the given slippage tolerance
/**
* Get the minimum amount that must be received from this trade for the given slippage tolerance
* @param slippageTolerance tolerance of unfavorable slippage from the execution price of this trade
*/
public minimumAmountOut(slippageTolerance: Percent): CurrencyAmount {
invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE')
if (this.tradeType === TradeType.EXACT_OUTPUT) {
......@@ -193,7 +196,10 @@ export class Trade {
}
}
// get the maximum amount in that can be spent via this trade for the given slippage tolerance
/**
* Get the maximum amount in that can be spent via this trade for the given slippage tolerance
* @param slippageTolerance tolerance of unfavorable slippage from the execution price of this trade
*/
public maximumAmountIn(slippageTolerance: Percent): CurrencyAmount {
invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE')
if (this.tradeType === TradeType.EXACT_INPUT) {
......@@ -206,10 +212,20 @@ export class Trade {
}
}
// given a list of pairs, and a fixed amount in, returns the top `maxNumResults` trades that go from an input token
// amount to an output token, making at most `maxHops` hops
// note this does not consider aggregation, as routes are linear. it's possible a better route exists by splitting
// the amount in among multiple routes.
/**
* Given a list of pairs, and a fixed amount in, returns the top `maxNumResults` trades that go from an input token
* amount to an output token, making at most `maxHops` hops.
* Note this does not consider aggregation, as routes are linear. It's possible a better route exists by splitting
* the amount in among multiple routes.
* @param pairs the pairs to consider in finding the best trade
* @param currencyAmountIn exact amount of input currency to spend
* @param currencyOut the desired currency out
* @param maxNumResults maximum number of results to return
* @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pair
* @param currentPairs used in recursion; the current list of pairs
* @param originalAmountIn used in recursion; the original value of the currencyAmountIn parameter
* @param bestTrades used in recursion; the current list of best trades
*/
public static bestTradeExactIn(
pairs: Pair[],
currencyAmountIn: CurrencyAmount,
......@@ -283,11 +299,21 @@ export class Trade {
return bestTrades
}
// similar to the above method but instead targets a fixed output amount
// given a list of pairs, and a fixed amount out, returns the top `maxNumResults` trades that go from an input token
// to an output token amount, making at most `maxHops` hops
// note this does not consider aggregation, as routes are linear. it's possible a better route exists by splitting
// the amount in among multiple routes.
/**
* similar to the above method but instead targets a fixed output amount
* given a list of pairs, and a fixed amount out, returns the top `maxNumResults` trades that go from an input token
* to an output token amount, making at most `maxHops` hops
* note this does not consider aggregation, as routes are linear. it's possible a better route exists by splitting
* the amount in among multiple routes.
* @param pairs the pairs to consider in finding the best trade
* @param currencyIn the currency to spend
* @param currencyAmountOut the exact amount of currency out
* @param maxNumResults maximum number of results to return
* @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pair
* @param currentPairs used in recursion; the current list of pairs
* @param originalAmountOut used in recursion; the original value of the currencyAmountOut parameter
* @param bestTrades used in recursion; the current list of best trades
*/
public static bestTradeExactOut(
pairs: Pair[],
currencyIn: Currency,
......
// see https://stackoverflow.com/a/41102306
const CAN_SET_PROTOTYPE = 'setPrototypeOf' in Object
/**
* Indicates that the pair has insufficient reserves for a desired output amount. I.e. the amount of output cannot be
* obtained by sending any amount of input.
*/
export class InsufficientReservesError extends Error {
public readonly isInsufficientReservesError: true = true
......@@ -11,6 +15,10 @@ export class InsufficientReservesError extends Error {
}
}
/**
* Indicates that the input amount is too small to produce any amount of output. I.e. the amount of input sent is less
* than the price of a single unit of output after fees.
*/
export class InsufficientInputAmountError extends Error {
public readonly isInsufficientInputAmountError: true = true
......
import { Contract } from '@ethersproject/contracts'
import { getNetwork } from '@ethersproject/networks'
import { getDefaultProvider } from '@ethersproject/providers'
import { TokenAmount } from './entities/fractions/tokenAmount'
import { Pair } from './entities/pair'
import IUniswapV2Pair from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import invariant from 'tiny-invariant'
import ERC20 from './abis/ERC20.json'
import { ChainId } from './constants'
import { Token } from './entities/token'
let TOKEN_DECIMALS_CACHE: { [chainId: number]: { [address: string]: number } } = {
[ChainId.MAINNET]: {
'0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A': 9 // DGD
}
}
/**
* Contains methods for constructing instances of pairs and tokens from on-chain data.
*/
export abstract class Fetcher {
/**
* Cannot be constructed.
*/
private constructor() {}
/**
* Fetch information for a given token on the given chain, using the given ethers provider.
* @param chainId chain of the token
* @param address address of the token on the chain
* @param provider provider used to fetch the token
* @param symbol optional symbol of the token
* @param name optional name of the token
*/
public static async fetchTokenData(
chainId: ChainId,
address: string,
provider = getDefaultProvider(getNetwork(chainId)),
symbol?: string,
name?: string
): Promise<Token> {
const parsedDecimals =
typeof TOKEN_DECIMALS_CACHE?.[chainId]?.[address] === 'number'
? TOKEN_DECIMALS_CACHE[chainId][address]
: await new Contract(address, ERC20, provider).decimals().then((decimals: number): number => {
TOKEN_DECIMALS_CACHE = {
...TOKEN_DECIMALS_CACHE,
[chainId]: {
...TOKEN_DECIMALS_CACHE?.[chainId],
[address]: decimals
}
}
return decimals
})
return new Token(chainId, address, parsedDecimals, symbol, name)
}
/**
* Fetches information about a pair and constructs a pair from the given two tokens.
* @param tokenA first token
* @param tokenB second token
* @param provider the provider to use to fetch the data
*/
public static async fetchPairData(
tokenA: Token,
tokenB: Token,
provider = getDefaultProvider(getNetwork(tokenA.chainId))
): Promise<Pair> {
invariant(tokenA.chainId === tokenB.chainId, 'CHAIN_ID')
const address = Pair.getAddress(tokenA, tokenB)
const [reserves0, reserves1] = await new Contract(address, IUniswapV2Pair.abi, provider).getReserves()
const balances = tokenA.sortsBefore(tokenB) ? [reserves0, reserves1] : [reserves1, reserves0]
return new Pair(new TokenAmount(tokenA, balances[0]), new TokenAmount(tokenB, balances[1]))
}
}
......@@ -14,3 +14,4 @@ export {
export * from './errors'
export * from './entities'
export * from './router'
export * from './fetcher'
......@@ -3,26 +3,46 @@ import invariant from 'tiny-invariant'
import { validateAndParseAddress } from './utils'
import { CurrencyAmount, ETHER, Percent, Trade } from './entities'
/**
* Options for producing the arguments to send call to the router.
*/
export interface TradeOptions {
// how much the execution price is allowed to move unfavorably from the trade execution price
/**
* How much the execution price is allowed to move unfavorably from the trade execution price.
*/
allowedSlippage: Percent
// how long the swap is valid until it expires, in seconds
// this will be used to produce a `deadline` parameter which is computed from when the swap call parameters
// are generated.
/**
* How long the swap is valid until it expires, in seconds.
* This will be used to produce a `deadline` parameter which is computed from when the swap call parameters
* are generated.
*/
ttl: number
// the account that should receive the output of the swap
/**
* The account that should receive the output of the swap.
*/
recipient: string
// whether any of the tokens in the path are fee on transfer tokens, which should be handled with special methods
/**
* Whether any of the tokens in the path are fee on transfer tokens, which should be handled with special methods
*/
feeOnTransfer?: boolean
}
/**
* The parameters to use in the call to the Uniswap V2 Router to execute a trade.
*/
export interface SwapParameters {
// the method to call on the Uniswap V2 Router
/**
* The method to call on the Uniswap V2 Router.
*/
methodName: string
// the arguments to pass to the method, all hex encoded
/**
* The arguments to pass to the method, all hex encoded.
*/
args: (string | string[])[]
// the amount of wei to send in hex
/**
* The amount of wei to send in hex.
*/
value: string
}
......
import { ChainId, WETH, Token, Pair } from '../src'
import { ChainId, WETH, Token, Fetcher } from '../src'
// TODO: replace the provider in these tests
describe.skip('data', () => {
it('Token', async () => {
const token = await Token.fetchData(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F') // DAI
const token = await Fetcher.fetchTokenData(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F') // DAI
expect(token.decimals).toEqual(18)
})
it('Token:CACHE', async () => {
const token = await Token.fetchData(ChainId.MAINNET, '0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A') // DGD
const token = await Fetcher.fetchTokenData(ChainId.MAINNET, '0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A') // DGD
expect(token.decimals).toEqual(9)
})
it('Pair', async () => {
const token = new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18) // DAI
const pair = await Pair.fetchData(WETH[ChainId.RINKEBY], token)
expect(pair.liquidityToken.address).toEqual('0x8507903035cFc0Bc7c8c62334dea16E2DA1EcecD')
const pair = await Fetcher.fetchPairData(WETH[ChainId.RINKEBY], token)
expect(pair.liquidityToken.address).toEqual('0x8B22F85d0c844Cf793690F6D9DFE9F11Ddb35449')
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment