Commit 1fcc0a59 authored by Noah Zinsmeister's avatar Noah Zinsmeister

finalize MVP beta release

add transact logic

augment tests
parent 96a412f6
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { BigNumberish } from '../../types' import { BigNumberish } from '../types'
import { normalizeBigNumberish } from '../../_utils' import { normalizeBigNumberish } from '../_utils'
interface TestCase { interface TestCase {
input: BigNumberish input: BigNumberish
expectedOutput: BigNumber expectedOutput: BigNumber
} }
function constructTestCase(input: BigNumberish, expectedOutput: BigNumber): TestCase { function constructTestCase(input: BigNumberish, expectedOutput: BigNumber): TestCase {
return { input, expectedOutput } return { input, expectedOutput }
} }
...@@ -52,9 +53,9 @@ describe('normalizeBigNumberish', (): void => { ...@@ -52,9 +53,9 @@ describe('normalizeBigNumberish', (): void => {
describe('number', (): void => { describe('number', (): void => {
const expectedSuccesses: TestCase[] = [ const expectedSuccesses: TestCase[] = [
constructTestCase(0, new BigNumber(0)), constructTestCase(0, new BigNumber('0')),
constructTestCase(1, new BigNumber(1)), constructTestCase(1, new BigNumber('1')),
constructTestCase(1.234, new BigNumber(1.234)) constructTestCase(1.234, new BigNumber('1.234'))
] ]
const expectedFailures: number[] = [NaN, Infinity] const expectedFailures: number[] = [NaN, Infinity]
...@@ -64,8 +65,8 @@ describe('normalizeBigNumberish', (): void => { ...@@ -64,8 +65,8 @@ describe('normalizeBigNumberish', (): void => {
describe('BigNumber', (): void => { describe('BigNumber', (): void => {
const expectedSuccesses: TestCase[] = [ const expectedSuccesses: TestCase[] = [
constructTestCase(new BigNumber(0), new BigNumber(0)), constructTestCase(new BigNumber(0), new BigNumber('0')),
constructTestCase(new BigNumber(1), new BigNumber(1)), constructTestCase(new BigNumber(1), new BigNumber('1')),
constructTestCase(new BigNumber('1.234'), new BigNumber('1.234')) constructTestCase(new BigNumber('1.234'), new BigNumber('1.234'))
] ]
const expectedFailures: BigNumber[] = [new BigNumber(NaN)] const expectedFailures: BigNumber[] = [new BigNumber(NaN)]
...@@ -76,8 +77,8 @@ describe('normalizeBigNumberish', (): void => { ...@@ -76,8 +77,8 @@ describe('normalizeBigNumberish', (): void => {
describe('ethers.utils.BigNumber', (): void => { describe('ethers.utils.BigNumber', (): void => {
const expectedSuccesses: TestCase[] = [ const expectedSuccesses: TestCase[] = [
constructTestCase(ethers.constants.Zero, new BigNumber(0)), constructTestCase(ethers.constants.Zero, new BigNumber('0')),
constructTestCase(ethers.constants.One, new BigNumber(1)), constructTestCase(ethers.constants.One, new BigNumber('1')),
constructTestCase(ethers.utils.bigNumberify('1234'), new BigNumber('1234')), constructTestCase(ethers.utils.bigNumberify('1234'), new BigNumber('1234')),
constructTestCase(ethers.utils.parseUnits('1.234', 3), new BigNumber('1234')) constructTestCase(ethers.utils.parseUnits('1.234', 3), new BigNumber('1234'))
] ]
......
import BigNumber from 'bignumber.js'
import { BigNumberish, TokenReserves, MarketDetails, OptionalReserves, TradeDetails } from '../types' import { BigNumberish, TokenReserves, MarketDetails, OptionalReserves, TradeDetails } from '../types'
import { TRADE_TYPE, TRADE_EXACT, _10 } from '../constants' import { TRADE_EXACT } from '../constants'
import { getMarketDetails, getTradeDetails } from '../computation' import { getMarketDetails, getTradeDetails } from '../computation'
function constructTokenReserves( function constructTokenReserves(
...@@ -16,80 +14,55 @@ function constructTokenReserves( ...@@ -16,80 +14,55 @@ function constructTokenReserves(
} }
} }
const DAIReserves: TokenReserves = constructTokenReserves(18, '4039700561005906883487', '1094055210563660633471343')
const USDCReserves: TokenReserves = constructTokenReserves(6, '1076592291503763426634', '292657693901')
function testMarketRates( function testMarketRates(
tradeType: TRADE_TYPE,
inputTokenReserves: OptionalReserves, inputTokenReserves: OptionalReserves,
outputTokenReserves: OptionalReserves, outputTokenReserves: OptionalReserves,
expectedMarketRate: string, expectedMarketRate: string,
expectedMarketRateInverted: string expectedMarketRateInverted: string
): void { ): void {
test('not inverted', (): void => { test('normal', (): void => {
const marketDetails: MarketDetails = getMarketDetails(tradeType, inputTokenReserves, outputTokenReserves) const marketDetails: MarketDetails = getMarketDetails(inputTokenReserves, outputTokenReserves)
expect(marketDetails.marketRate.rate.toFixed(18)).toBe(expectedMarketRate) expect(marketDetails.marketRate.rate.toFixed(18)).toBe(expectedMarketRate)
expect(marketDetails.marketRate.rateInverted.toFixed(18)).toBe(expectedMarketRateInverted) expect(marketDetails.marketRate.rateInverted.toFixed(18)).toBe(expectedMarketRateInverted)
}) })
test('manually inverted', (): void => { test('inverted', (): void => {
const tradeTypeInverted = tradeType === TRADE_TYPE.ETH_TO_TOKEN ? TRADE_TYPE.TOKEN_TO_ETH : TRADE_TYPE.ETH_TO_TOKEN const marketDetails: MarketDetails = getMarketDetails(outputTokenReserves, inputTokenReserves)
const marketDetails: MarketDetails = getMarketDetails(
tradeType === TRADE_TYPE.TOKEN_TO_TOKEN ? TRADE_TYPE.TOKEN_TO_TOKEN : tradeTypeInverted,
outputTokenReserves,
inputTokenReserves
)
expect(marketDetails.marketRate.rate.toFixed(18)).toBe(expectedMarketRateInverted) expect(marketDetails.marketRate.rate.toFixed(18)).toBe(expectedMarketRateInverted)
expect(marketDetails.marketRate.rateInverted.toFixed(18)).toBe(expectedMarketRate) expect(marketDetails.marketRate.rateInverted.toFixed(18)).toBe(expectedMarketRate)
}) })
} }
describe('getMarketDetails', (): void => { describe('getMarketDetails', (): void => {
describe('dummy ETH/DAI and DAI/ETH', (): void => { describe('DAI', (): void => {
const tokenReserves: TokenReserves = constructTokenReserves(
18,
'4039700561005906883487',
'1094055210563660633471343'
)
const expectedMarketRate = '270.825818409480102284' const expectedMarketRate = '270.825818409480102284'
const expectedMarketRateInverted = '0.003692410147130181' const expectedMarketRateInverted = '0.003692410147130181'
testMarketRates(undefined, DAIReserves, expectedMarketRate, expectedMarketRateInverted)
testMarketRates(TRADE_TYPE.ETH_TO_TOKEN, null, tokenReserves, expectedMarketRate, expectedMarketRateInverted)
}) })
describe('dummy ETH/USDC and USDC/ETH', (): void => { describe('USDC', (): void => {
const tokenReserves: TokenReserves = constructTokenReserves(6, '1076592291503763426634', '292657693901')
const expectedMarketRate = '0.003678674143683891' const expectedMarketRate = '0.003678674143683891'
const expectedMarketRateInverted = '271.837069808684359442' const expectedMarketRateInverted = '271.837069808684359442'
testMarketRates(USDCReserves, undefined, expectedMarketRate, expectedMarketRateInverted)
testMarketRates(TRADE_TYPE.TOKEN_TO_ETH, tokenReserves, null, expectedMarketRate, expectedMarketRateInverted)
}) })
describe('dummy DAI/USDC and USDC/DAI', (): void => { describe('DAI and USDC', (): void => {
const DAITokenReserves: TokenReserves = constructTokenReserves(
18,
'4039700561005906883487',
'1094055210563660633471343'
)
const USDCTokenReserves: TokenReserves = constructTokenReserves(6, '1076592291503763426634', '292657693901')
const expectedMarketRate = '1.003733954927721392' const expectedMarketRate = '1.003733954927721392'
const expectedMarketRateInverted = '0.996279935624983143' const expectedMarketRateInverted = '0.996279935624983143'
testMarketRates( testMarketRates(DAIReserves, USDCReserves, expectedMarketRate, expectedMarketRateInverted)
TRADE_TYPE.TOKEN_TO_TOKEN,
DAITokenReserves,
USDCTokenReserves,
expectedMarketRate,
expectedMarketRateInverted
)
}) })
}) })
function testTradeDetails( function testTradeDetails(
tradeExact: TRADE_EXACT, tradeExact: TRADE_EXACT,
tradeAmount: BigNumber, tradeAmount: BigNumberish,
marketDetails: MarketDetails, marketDetails: MarketDetails,
expectedInputValue: string, expectedInputValue: string,
expectedOutputValue: string,
expectedExecutionRate: string, expectedExecutionRate: string,
expectedExecutionRateInverted: string, expectedExecutionRateInverted: string,
expectedMarketRateSlippage: string, expectedMarketRateSlippage: string,
...@@ -99,6 +72,8 @@ function testTradeDetails( ...@@ -99,6 +72,8 @@ function testTradeDetails(
const tradeDetails: TradeDetails = getTradeDetails(tradeExact, tradeAmount, marketDetails) const tradeDetails: TradeDetails = getTradeDetails(tradeExact, tradeAmount, marketDetails)
expect(tradeDetails.inputAmount.amount.toFixed(0)).toBe(expectedInputValue) expect(tradeDetails.inputAmount.amount.toFixed(0)).toBe(expectedInputValue)
expect(tradeDetails.outputAmount.amount.toFixed(0)).toBe(expectedOutputValue)
expect(tradeDetails.executionRate.rate.toFixed(18)).toBe(expectedExecutionRate) expect(tradeDetails.executionRate.rate.toFixed(18)).toBe(expectedExecutionRate)
expect(tradeDetails.executionRate.rateInverted.toFixed(18)).toBe(expectedExecutionRateInverted) expect(tradeDetails.executionRate.rateInverted.toFixed(18)).toBe(expectedExecutionRateInverted)
...@@ -108,25 +83,68 @@ function testTradeDetails( ...@@ -108,25 +83,68 @@ function testTradeDetails(
} }
describe('getTradeDetails', (): void => { describe('getTradeDetails', (): void => {
describe('dummy ETH/DAI and DAI/ETH', (): void => { describe('ETH for DAI exact output', (): void => {
const tokenReserves: TokenReserves = constructTokenReserves( const marketDetails: MarketDetails = getMarketDetails(undefined, DAIReserves)
18,
'4039700561005906883487',
'1094055210563660633471343'
)
const expectedInputValue = '370385925334764803' const expectedInputValue = '370385925334764803'
const expectedOutputValue = '100000000000000000000'
const expectedExecutionRate = '269.988660907180258319' const expectedExecutionRate = '269.988660907180258319'
const expectedExecutionRateInverted = '0.003703859253347648' const expectedExecutionRateInverted = '0.003703859253347648'
const expectedMarketRateSlippage = '1.830727602963479922' const expectedMarketRateSlippage = '1.830727602963479922'
const expectedExecutionRateSlippage = '30.911288562381013644' const expectedExecutionRateSlippage = '30.911288562381013644'
const marketDetails: MarketDetails = getMarketDetails(TRADE_TYPE.ETH_TO_TOKEN, null, tokenReserves)
testTradeDetails( testTradeDetails(
TRADE_EXACT.OUTPUT, TRADE_EXACT.OUTPUT,
new BigNumber(100).multipliedBy(_10.exponentiatedBy(18)), expectedOutputValue,
marketDetails,
expectedInputValue,
expectedOutputValue,
expectedExecutionRate,
expectedExecutionRateInverted,
expectedMarketRateSlippage,
expectedExecutionRateSlippage
)
})
describe('DAI for ETH exact output', (): void => {
const marketDetails: MarketDetails = getMarketDetails(DAIReserves, undefined)
const expectedInputValue = '271708000072010674989'
const expectedOutputValue = '1000000000000000000'
const expectedExecutionRate = '0.003680421628126409'
const expectedExecutionRateInverted = '271.708000072010674989'
const expectedMarketRateSlippage = '4.957694170529155578'
const expectedExecutionRateSlippage = '32.468004707141566275'
testTradeDetails(
TRADE_EXACT.OUTPUT,
expectedOutputValue,
marketDetails,
expectedInputValue,
expectedOutputValue,
expectedExecutionRate,
expectedExecutionRateInverted,
expectedMarketRateSlippage,
expectedExecutionRateSlippage
)
})
describe('DAI for USDC exact input', (): void => {
const marketDetails: MarketDetails = getMarketDetails(DAIReserves, USDCReserves)
const expectedInputValue = '1000000000000000000'
const expectedOutputValue = '997716'
const expectedExecutionRate = '0.997716000000000000'
const expectedExecutionRateInverted = '1.002289228598118102'
const expectedMarketRateSlippage = '0.086538655703617094'
const expectedExecutionRateSlippage = '59.955677479843185050'
testTradeDetails(
TRADE_EXACT.INPUT,
expectedInputValue,
marketDetails, marketDetails,
expectedInputValue, expectedInputValue,
expectedOutputValue,
expectedExecutionRate, expectedExecutionRate,
expectedExecutionRateInverted, expectedExecutionRateInverted,
expectedMarketRateSlippage, expectedMarketRateSlippage,
......
import { TokenReserves, Token } from '../types' import { Token, TokenReservesNormalized } from '../types'
import { ETH as _ETH } from '../constants' import { ETH as _ETH, _CHAIN_ID_NAME } from '../constants'
import { getTokenReserves } from '../data' import { getTokenReserves, getEthToken } from '../data'
import { ethers } from 'ethers'
const ETH: Token = { const ETH: Token = {
chainId: 1, chainId: 1,
...@@ -21,9 +22,17 @@ const DAI_EXCHANGE: Token = { ...@@ -21,9 +22,17 @@ const DAI_EXCHANGE: Token = {
} }
describe('getTokenReserves', (): void => { describe('getTokenReserves', (): void => {
test('DAI', async (done: jest.DoneCallback): Promise<void> => {
jest.setTimeout(10000) // 10 seconds jest.setTimeout(10000) // 10 seconds
const tokenReserves: TokenReserves = await getTokenReserves(DAI.address as string)
test('DAI', async (done: jest.DoneCallback): Promise<void> => {
const tokenReserves: TokenReservesNormalized = await getTokenReserves(DAI.address as string)
const tokenReservesProvider: TokenReservesNormalized = await getTokenReserves(
DAI.address as string,
ethers.getDefaultProvider(_CHAIN_ID_NAME[1])
)
expect(tokenReserves.token).toEqual(tokenReservesProvider.token)
expect(tokenReserves.token).toEqual(DAI) expect(tokenReserves.token).toEqual(DAI)
expect(tokenReserves.exchange).toEqual(DAI_EXCHANGE) expect(tokenReserves.exchange).toEqual(DAI_EXCHANGE)
...@@ -35,3 +44,7 @@ describe('getTokenReserves', (): void => { ...@@ -35,3 +44,7 @@ describe('getTokenReserves', (): void => {
done() done()
}) })
}) })
test('getEthToken', (): void => {
expect(getEthToken(1)).toEqual(ETH)
})
...@@ -2,11 +2,11 @@ import BigInteger from 'bignumber.js' ...@@ -2,11 +2,11 @@ import BigInteger from 'bignumber.js'
import { FlexibleFormat, FormatSignificantOptions, FormatFixedOptions } from '../types' import { FlexibleFormat, FormatSignificantOptions, FormatFixedOptions } from '../types'
import { formatSignificant, formatSignificantDecimals, formatFixed, formatFixedDecimals } from '../format' import { formatSignificant, formatSignificantDecimals, formatFixed, formatFixedDecimals } from '../format'
import { FIXED_UNDERFLOW_BEHAVIOR, ROUNDING_MODE } from '../constants' import { FIXED_UNDERFLOW_BEHAVIOR, _ROUNDING_MODE } from '../constants'
function constructFormatSignificantOptions( function constructFormatSignificantOptions(
significantDigits: number, significantDigits: number,
roundingMode: BigInteger.RoundingMode = ROUNDING_MODE, roundingMode: BigInteger.RoundingMode = _ROUNDING_MODE,
forceIntegerSignificance: boolean = false, forceIntegerSignificance: boolean = false,
format: FlexibleFormat = false format: FlexibleFormat = false
): FormatSignificantOptions { ): FormatSignificantOptions {
...@@ -20,7 +20,7 @@ function constructFormatSignificantOptions( ...@@ -20,7 +20,7 @@ function constructFormatSignificantOptions(
function constructFormatFixedOptions( function constructFormatFixedOptions(
decimalPlaces: number, decimalPlaces: number,
roundingMode: BigInteger.RoundingMode = ROUNDING_MODE, roundingMode: BigInteger.RoundingMode = _ROUNDING_MODE,
dropTrailingZeros: boolean = true, dropTrailingZeros: boolean = true,
underflowBehavior: FIXED_UNDERFLOW_BEHAVIOR = FIXED_UNDERFLOW_BEHAVIOR.ONE_DIGIT, underflowBehavior: FIXED_UNDERFLOW_BEHAVIOR = FIXED_UNDERFLOW_BEHAVIOR.ONE_DIGIT,
format: FlexibleFormat = false format: FlexibleFormat = false
......
import BigNumber from 'bignumber.js'
import { TradeDetails } from '../types' import { TradeDetails } from '../types'
import { _10, ETH, TRADE_TYPE, TRADE_EXACT } from '../constants' import { tradeExactEthForTokens } from '../orchestration'
import { getTrade } from '../orchestration' import { TRADE_TYPE, TRADE_EXACT } from '../constants'
describe('getTrade', (): void => { describe('tradeExactEthForTokens', (): void => {
test('DAI', async (done: jest.DoneCallback): Promise<void> => {
jest.setTimeout(10000) // 10 seconds jest.setTimeout(10000) // 10 seconds
const tradeDetails: TradeDetails = await getTrade(
ETH, test('DAI', async (done: jest.DoneCallback): Promise<void> => {
const tradeDetails: TradeDetails = await tradeExactEthForTokens(
'0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359', '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
TRADE_TYPE.ETH_TO_TOKEN, '1000000000000000000'
TRADE_EXACT.OUTPUT,
new BigNumber(100).multipliedBy(_10.exponentiatedBy(18))
) )
expect(tradeDetails.inputAmount).toBeTruthy()
expect(tradeDetails.tradeType).toEqual(TRADE_TYPE.ETH_TO_TOKEN)
expect(tradeDetails.tradeExact).toEqual(TRADE_EXACT.INPUT)
expect(tradeDetails.inputAmount.amount.toFixed()).toEqual('1000000000000000000')
expect(tradeDetails.outputAmount.amount).toBeTruthy()
expect(tradeDetails.executionRate.rate).toBeTruthy()
expect(tradeDetails.executionRate.rateInverted).toBeTruthy()
expect(tradeDetails.marketRateSlippage).toBeTruthy()
expect(tradeDetails.executionRateSlippage).toBeTruthy()
done() done()
}) })
}) })
import { TradeDetails, ExecutionDetails } from '../types'
import { TRADE_METHODS, TRADE_METHOD_IDS } from '../constants'
import { tradeExactEthForTokens } from '../orchestration'
import { getExecutionDetails } from '../transact'
describe('tradeExactEthForTokens', (): void => {
jest.setTimeout(10000) // 10 seconds
test('DAI', async (done: jest.DoneCallback): Promise<void> => {
const tradeDetails: TradeDetails = await tradeExactEthForTokens(
'0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
'1000000000000000000'
)
const executionDetails: ExecutionDetails = getExecutionDetails(tradeDetails)
expect(executionDetails.exchangeAddress).toEqual('0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14')
expect(executionDetails.methodName).toEqual(TRADE_METHODS.ethToTokenSwapInput)
expect(executionDetails.methodId).toEqual(TRADE_METHOD_IDS[TRADE_METHODS.ethToTokenSwapInput])
expect(executionDetails.value.toFixed()).toEqual('1000000000000000000')
done()
})
})
...@@ -2,11 +2,10 @@ import BigNumber from 'bignumber.js' ...@@ -2,11 +2,10 @@ import BigNumber from 'bignumber.js'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { BigNumberish } from '../types' import { BigNumberish } from '../types'
import { _0, MAX_UINT8, MAX_UINT256 } from '../constants' import { _0, _MAX_UINT8, _MAX_UINT256 } from '../constants'
// check(s) that number(s) is(are) uint8(s)
function ensureUInt8(number: number): void { function ensureUInt8(number: number): void {
if (!Number.isInteger(number) || number < 0 || number > MAX_UINT8) { if (!Number.isInteger(number) || number < 0 || number > _MAX_UINT8) {
throw Error(`Passed number '${number}' is not a valid uint8.`) throw Error(`Passed number '${number}' is not a valid uint8.`)
} }
} }
...@@ -15,9 +14,8 @@ export function ensureAllUInt8(numbers: number[]): void { ...@@ -15,9 +14,8 @@ export function ensureAllUInt8(numbers: number[]): void {
numbers.forEach(ensureUInt8) numbers.forEach(ensureUInt8)
} }
// check(s) that BigNumber(s) is(are) uint256(s)
function ensureUInt256(bigNumber: BigNumber): void { function ensureUInt256(bigNumber: BigNumber): void {
if (!bigNumber.isInteger() || bigNumber.isLessThan(_0) || bigNumber.isGreaterThan(MAX_UINT256)) { if (!bigNumber.isInteger() || bigNumber.isLessThan(_0) || bigNumber.isGreaterThan(_MAX_UINT256)) {
throw Error(`Passed bigNumber '${bigNumber}' is not a valid uint256.`) throw Error(`Passed bigNumber '${bigNumber}' is not a valid uint256.`)
} }
} }
...@@ -26,31 +24,26 @@ export function ensureAllUInt256(bigNumbers: BigNumber[]): void { ...@@ -26,31 +24,26 @@ export function ensureAllUInt256(bigNumbers: BigNumber[]): void {
bigNumbers.forEach(ensureUInt256) bigNumbers.forEach(ensureUInt256)
} }
// check that number is valid decimals places/significant digits
export function ensureBoundedInteger(number: number, bounds: number | number[]): void { export function ensureBoundedInteger(number: number, bounds: number | number[]): void {
const [minimum, maximum]: [number, number] = typeof bounds === 'number' ? [0, bounds] : [bounds[0], bounds[1]] const [minimum, maximum]: [number, number] = typeof bounds === 'number' ? [0, bounds] : [bounds[0], bounds[1]]
if (!Number.isInteger(number) || number < minimum || number > maximum) { if (!Number.isInteger(number) || number < minimum || number > maximum) {
throw Error(`Passed number '${number}' is not an integer between '${minimum}' and '${maximum}', inclusive.`) throw Error(`Passed number '${number}' is not an integer between '${minimum}' and '${maximum}' (inclusive).`)
} }
} }
export function normalizeBigNumberish(bigNumberish: BigNumberish): BigNumber { export function normalizeBigNumberish(bigNumberish: BigNumberish): BigNumber {
try { const bigNumber: BigNumber = BigNumber.isBigNumber(bigNumberish)
const bigNumber = BigNumber.isBigNumber(bigNumberish) ? bigNumberish : new BigNumber(bigNumberish.toString()) ? bigNumberish
: new BigNumber(bigNumberish.toString())
if (!bigNumber.isFinite()) { if (!bigNumber.isFinite()) {
throw Error throw Error(`Passed bigNumberish '${bigNumberish}' of type '${typeof bigNumberish}' is not valid.`)
} }
return bigNumber return bigNumber
} catch (error) {
throw Error(`Passed bigNumberish '${bigNumberish}' of type '${typeof bigNumberish}' is invalid. Error: '${error}'.`)
}
} }
export function normalizeAddress(address: string): string { export function normalizeAddress(address: string): string {
try {
return ethers.utils.getAddress(address.toLowerCase()) return ethers.utils.getAddress(address.toLowerCase())
} catch {
throw Error(`Passed address '${address}' is not valid.`)
}
} }
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { import { TokenAmountNormalized, _DecimalRate, _AnyRate } from '../types'
TokenAmount, import { _1, _10 } from '../constants'
TokenAmountNormalized,
TokenReserves,
areTokenReserves,
areETHReserves,
OptionalReserves,
TokenReservesNormalized,
_ParsedOptionalReserves,
_DecimalRate,
_AnyRate
} from '../types'
import { _1, _10, _10000, TRADE_TYPE, ETH_TOKEN } from '../constants'
import { ensureAllUInt8, ensureAllUInt256, normalizeBigNumberish } from '../_utils'
function normalizeTokenAmount(tokenAmount: TokenAmount): TokenAmountNormalized {
ensureAllUInt8([tokenAmount.token.decimals])
const normalizedAmount: BigNumber = normalizeBigNumberish(tokenAmount.amount)
ensureAllUInt256([normalizedAmount])
return {
token: { ...tokenAmount.token },
amount: normalizedAmount
}
}
function normalizeTokenReserves(tokenReserves: TokenReserves): TokenReservesNormalized {
ensureAllUInt8([tokenReserves.token.decimals])
if (tokenReserves.exchange) {
ensureAllUInt8([tokenReserves.exchange.decimals])
}
return {
token: { ...tokenReserves.token },
...(tokenReserves.exchange ? { ...tokenReserves.exchange } : {}),
ethReserve: normalizeTokenAmount(tokenReserves.ethReserve),
tokenReserve: normalizeTokenAmount(tokenReserves.tokenReserve)
}
}
function ensureTradeTypesMatch(computedTradeType: TRADE_TYPE, passedTradeType?: TRADE_TYPE): never | void {
if (passedTradeType && passedTradeType !== computedTradeType) {
throw Error(`passedTradeType '${passedTradeType}' does not match computedTradeType '${computedTradeType}'.`)
}
}
export function parseOptionalReserves(
optionalReservesInput: OptionalReserves,
optionalReservesOutput: OptionalReserves,
passedTradeType?: TRADE_TYPE
): _ParsedOptionalReserves {
if (areTokenReserves(optionalReservesInput) && areTokenReserves(optionalReservesOutput)) {
const computedTradeType: TRADE_TYPE = TRADE_TYPE.TOKEN_TO_TOKEN
ensureTradeTypesMatch(computedTradeType, passedTradeType)
return {
tradeType: computedTradeType,
inputReserves: normalizeTokenReserves(optionalReservesInput),
outputReserves: normalizeTokenReserves(optionalReservesOutput)
}
} else if (areTokenReserves(optionalReservesInput) && !areTokenReserves(optionalReservesOutput)) {
const computedTradeType: TRADE_TYPE = TRADE_TYPE.TOKEN_TO_ETH
ensureTradeTypesMatch(computedTradeType, passedTradeType)
return {
tradeType: computedTradeType,
inputReserves: normalizeTokenReserves(optionalReservesInput),
outputReserves: {
token: ETH_TOKEN(areETHReserves(optionalReservesOutput) ? optionalReservesOutput.token.chainId : undefined)
}
}
} else if (!areTokenReserves(optionalReservesInput) && areTokenReserves(optionalReservesOutput)) {
const computedTradeType: TRADE_TYPE = TRADE_TYPE.ETH_TO_TOKEN
ensureTradeTypesMatch(computedTradeType, passedTradeType)
return {
tradeType: computedTradeType,
inputReserves: {
token: ETH_TOKEN(areETHReserves(optionalReservesInput) ? optionalReservesInput.token.chainId : undefined)
},
outputReserves: normalizeTokenReserves(optionalReservesOutput)
}
} else {
throw Error('optionalReservesInput, optionalReservesOutput, or both must be defined.')
}
}
export function calculateDecimalRate( export function calculateDecimalRate(
numerator: TokenAmountNormalized, numerator: TokenAmountNormalized,
...@@ -119,9 +34,3 @@ export function calculateDecimalRate( ...@@ -119,9 +34,3 @@ export function calculateDecimalRate(
.dividedBy(decimalRate.numerator) .dividedBy(decimalRate.numerator)
} }
} }
// slippage in basis points, to 18 decimals
export function calculateSlippage(baseRate: BigNumber, newRate: BigNumber): BigNumber {
const difference: BigNumber = baseRate.minus(newRate).absoluteValue()
return difference.multipliedBy(_10000).dividedBy(baseRate)
}
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { import {
TokenAmount,
TokenAmountNormalized, TokenAmountNormalized,
OptionalReserves, TokenReserves,
TokenReservesNormalized, TokenReservesNormalized,
NormalizedReserves,
areTokenReservesNormalized, areTokenReservesNormalized,
areETHReserves,
areTokenReserves,
OptionalReserves,
NormalizedReserves,
Rate, Rate,
MarketDetails, MarketDetails,
_ParsedOptionalReserves, _ParsedOptionalReserves,
...@@ -13,7 +17,67 @@ import { ...@@ -13,7 +17,67 @@ import {
_AnyRate _AnyRate
} from '../types' } from '../types'
import { TRADE_TYPE } from '../constants' import { TRADE_TYPE } from '../constants'
import { parseOptionalReserves, calculateDecimalRate } from './_utils' import { normalizeBigNumberish, ensureAllUInt8, ensureAllUInt256 } from '../_utils'
import { getEthToken } from '../data'
import { calculateDecimalRate } from './_utils'
function normalizeTokenAmount(tokenAmount: TokenAmount): TokenAmountNormalized {
ensureAllUInt8([tokenAmount.token.decimals])
const normalizedAmount: BigNumber = normalizeBigNumberish(tokenAmount.amount)
ensureAllUInt256([normalizedAmount])
return {
token: { ...tokenAmount.token },
amount: normalizedAmount
}
}
function normalizeTokenReserves(tokenReserves: TokenReserves): TokenReservesNormalized {
ensureAllUInt8([tokenReserves.token.decimals])
return {
token: { ...tokenReserves.token },
...(tokenReserves.exchange ? { exchange: { ...tokenReserves.exchange } } : {}),
ethReserve: normalizeTokenAmount(tokenReserves.ethReserve),
tokenReserve: normalizeTokenAmount(tokenReserves.tokenReserve)
}
}
function parseOptionalReserves(
optionalReservesInput: OptionalReserves,
optionalReservesOutput: OptionalReserves
): _ParsedOptionalReserves {
if (areTokenReserves(optionalReservesInput) && areTokenReserves(optionalReservesOutput)) {
return {
tradeType: TRADE_TYPE.TOKEN_TO_TOKEN,
inputReserves: normalizeTokenReserves(optionalReservesInput),
outputReserves: normalizeTokenReserves(optionalReservesOutput)
}
} else if (areTokenReserves(optionalReservesInput) && !areTokenReserves(optionalReservesOutput)) {
return {
tradeType: TRADE_TYPE.TOKEN_TO_ETH,
inputReserves: normalizeTokenReserves(optionalReservesInput),
outputReserves: areETHReserves(optionalReservesOutput)
? optionalReservesOutput
: {
token: getEthToken(optionalReservesInput.token.chainId)
}
}
} else if (!areTokenReserves(optionalReservesInput) && areTokenReserves(optionalReservesOutput)) {
return {
tradeType: TRADE_TYPE.ETH_TO_TOKEN,
inputReserves: areETHReserves(optionalReservesInput)
? optionalReservesInput
: {
token: getEthToken(optionalReservesOutput.token.chainId)
},
outputReserves: normalizeTokenReserves(optionalReservesOutput)
}
} else {
throw Error('optionalReservesInput, optionalReservesOutput, or both must be defined.')
}
}
// calculates the market rate for ETH_TO_TOKEN or TOKEN_TO_ETH trades // calculates the market rate for ETH_TO_TOKEN or TOKEN_TO_ETH trades
function getMarketRate(tradeType: TRADE_TYPE, reserves: NormalizedReserves, keepAsDecimal?: boolean): _AnyRate { function getMarketRate(tradeType: TRADE_TYPE, reserves: NormalizedReserves, keepAsDecimal?: boolean): _AnyRate {
...@@ -31,14 +95,12 @@ function getMarketRate(tradeType: TRADE_TYPE, reserves: NormalizedReserves, keep ...@@ -31,14 +95,12 @@ function getMarketRate(tradeType: TRADE_TYPE, reserves: NormalizedReserves, keep
// note: rounds rates to 18 decimal places // note: rounds rates to 18 decimal places
export function getMarketDetails( export function getMarketDetails(
tradeType: TRADE_TYPE,
optionalReservesInput: OptionalReserves, optionalReservesInput: OptionalReserves,
optionalReservesOutput: OptionalReserves optionalReservesOutput: OptionalReserves
): MarketDetails { ): MarketDetails {
const { inputReserves, outputReserves }: _ParsedOptionalReserves = parseOptionalReserves( const { tradeType, inputReserves, outputReserves }: _ParsedOptionalReserves = parseOptionalReserves(
optionalReservesInput, optionalReservesInput,
optionalReservesOutput, optionalReservesOutput
tradeType
) )
if (tradeType === TRADE_TYPE.TOKEN_TO_TOKEN) { if (tradeType === TRADE_TYPE.TOKEN_TO_TOKEN) {
......
...@@ -3,19 +3,20 @@ import cloneDeepWith from 'lodash.clonedeepwith' ...@@ -3,19 +3,20 @@ import cloneDeepWith from 'lodash.clonedeepwith'
import { import {
BigNumberish, BigNumberish,
Rate,
MarketDetails,
TradeDetails,
TokenAmountNormalized, TokenAmountNormalized,
areTokenReservesNormalized, areTokenReservesNormalized,
NormalizedReserves, NormalizedReserves,
Rate,
MarketDetails,
TradeDetails,
_PartialTradeDetails _PartialTradeDetails
} from '../types' } from '../types'
import { _0, _1, _997, _1000, TRADE_TYPE, TRADE_EXACT } from '../constants' import { _0, _1, _997, _1000, TRADE_TYPE, TRADE_EXACT, _10000 } from '../constants'
import { ensureAllUInt256, normalizeBigNumberish } from '../_utils' import { normalizeBigNumberish, ensureAllUInt256 } from '../_utils'
import { calculateDecimalRate, calculateSlippage } from './_utils' import { calculateDecimalRate } from './_utils'
import { getMarketDetails } from './market' import { getMarketDetails } from './market'
// emulates the uniswap smart contract logic
function getInputPrice(inputAmount: BigNumber, inputReserve: BigNumber, outputReserve: BigNumber): BigNumber { function getInputPrice(inputAmount: BigNumber, inputReserve: BigNumber, outputReserve: BigNumber): BigNumber {
ensureAllUInt256([inputAmount, inputReserve, outputReserve]) ensureAllUInt256([inputAmount, inputReserve, outputReserve])
...@@ -33,6 +34,7 @@ function getInputPrice(inputAmount: BigNumber, inputReserve: BigNumber, outputRe ...@@ -33,6 +34,7 @@ function getInputPrice(inputAmount: BigNumber, inputReserve: BigNumber, outputRe
return outputAmount return outputAmount
} }
// emulates the uniswap smart contract logic
function getOutputPrice(outputAmount: BigNumber, inputReserve: BigNumber, outputReserve: BigNumber): BigNumber { function getOutputPrice(outputAmount: BigNumber, inputReserve: BigNumber, outputReserve: BigNumber): BigNumber {
ensureAllUInt256([outputAmount, inputReserve, outputReserve]) ensureAllUInt256([outputAmount, inputReserve, outputReserve])
...@@ -49,7 +51,7 @@ function getOutputPrice(outputAmount: BigNumber, inputReserve: BigNumber, output ...@@ -49,7 +51,7 @@ function getOutputPrice(outputAmount: BigNumber, inputReserve: BigNumber, output
return inputAmount return inputAmount
} }
function _getTradeTransput( function getSingleTradeTransput(
tradeType: TRADE_TYPE, tradeType: TRADE_TYPE,
tradeExact: TRADE_EXACT, tradeExact: TRADE_EXACT,
tradeAmount: BigNumber, tradeAmount: BigNumber,
...@@ -72,11 +74,10 @@ function _getTradeTransput( ...@@ -72,11 +74,10 @@ function _getTradeTransput(
return calculatedAmount return calculatedAmount
} }
function customizer(value: BigNumber): BigNumber | undefined { function customizer(value: BigNumber): BigNumber | void {
if (BigNumber.isBigNumber(value)) { if (BigNumber.isBigNumber(value)) {
return new BigNumber(value) return new BigNumber(value)
} }
return
} }
// gets the corresponding input/output amount for the passed output/input amount // gets the corresponding input/output amount for the passed output/input amount
...@@ -96,13 +97,13 @@ function getTradeTransput( ...@@ -96,13 +97,13 @@ function getTradeTransput(
} }
if (tradeExact === TRADE_EXACT.INPUT) { if (tradeExact === TRADE_EXACT.INPUT) {
const intermediateTransput: BigNumber = _getTradeTransput( const intermediateTransput: BigNumber = getSingleTradeTransput(
TRADE_TYPE.TOKEN_TO_ETH, TRADE_TYPE.TOKEN_TO_ETH,
TRADE_EXACT.INPUT, TRADE_EXACT.INPUT,
tradeAmount, tradeAmount,
inputReserves inputReserves
) )
const finalTransput: BigNumber = _getTradeTransput( const finalTransput: BigNumber = getSingleTradeTransput(
TRADE_TYPE.ETH_TO_TOKEN, TRADE_TYPE.ETH_TO_TOKEN,
TRADE_EXACT.INPUT, TRADE_EXACT.INPUT,
intermediateTransput, intermediateTransput,
...@@ -120,13 +121,13 @@ function getTradeTransput( ...@@ -120,13 +121,13 @@ function getTradeTransput(
outputReservesPost outputReservesPost
} }
} else { } else {
const intermediateTransput: BigNumber = _getTradeTransput( const intermediateTransput: BigNumber = getSingleTradeTransput(
TRADE_TYPE.ETH_TO_TOKEN, TRADE_TYPE.ETH_TO_TOKEN,
TRADE_EXACT.OUTPUT, TRADE_EXACT.OUTPUT,
tradeAmount, tradeAmount,
outputReserves outputReserves
) )
const finalTransput: BigNumber = _getTradeTransput( const finalTransput: BigNumber = getSingleTradeTransput(
TRADE_TYPE.TOKEN_TO_ETH, TRADE_TYPE.TOKEN_TO_ETH,
TRADE_EXACT.OUTPUT, TRADE_EXACT.OUTPUT,
intermediateTransput, intermediateTransput,
...@@ -147,7 +148,7 @@ function getTradeTransput( ...@@ -147,7 +148,7 @@ function getTradeTransput(
} else { } else {
const reserves: NormalizedReserves = tradeType === TRADE_TYPE.ETH_TO_TOKEN ? outputReserves : inputReserves const reserves: NormalizedReserves = tradeType === TRADE_TYPE.ETH_TO_TOKEN ? outputReserves : inputReserves
const finalTransput: BigNumber = _getTradeTransput(tradeType, tradeExact, tradeAmount, reserves) const finalTransput: BigNumber = getSingleTradeTransput(tradeType, tradeExact, tradeAmount, reserves)
if (tradeType === TRADE_TYPE.ETH_TO_TOKEN) { if (tradeType === TRADE_TYPE.ETH_TO_TOKEN) {
if (!areTokenReservesNormalized(outputReservesPost)) { if (!areTokenReservesNormalized(outputReservesPost)) {
...@@ -181,13 +182,20 @@ function getTradeTransput( ...@@ -181,13 +182,20 @@ function getTradeTransput(
} }
} }
// slippage in basis points, to 18 decimals
function calculateSlippage(baseRate: BigNumber, newRate: BigNumber): BigNumber {
const difference: BigNumber = baseRate.minus(newRate).absoluteValue()
return difference.multipliedBy(_10000).dividedBy(baseRate)
}
export function getTradeDetails( export function getTradeDetails(
tradeExact: TRADE_EXACT, tradeExact: TRADE_EXACT,
_tradeAmount: BigNumberish, _tradeAmount: BigNumberish,
marketDetails: MarketDetails marketDetails: MarketDetails
): TradeDetails { ): TradeDetails {
// get other input/output amount
const tradeAmount: BigNumber = normalizeBigNumberish(_tradeAmount) const tradeAmount: BigNumber = normalizeBigNumberish(_tradeAmount)
// get other input/output amount
const { transput, inputReservesPost, outputReservesPost }: _PartialTradeDetails = getTradeTransput( const { transput, inputReservesPost, outputReservesPost }: _PartialTradeDetails = getTradeTransput(
marketDetails.tradeType, marketDetails.tradeType,
tradeExact, tradeExact,
...@@ -206,11 +214,7 @@ export function getTradeDetails( ...@@ -206,11 +214,7 @@ export function getTradeDetails(
amount: tradeExact === TRADE_EXACT.INPUT ? transput : tradeAmount amount: tradeExact === TRADE_EXACT.INPUT ? transput : tradeAmount
} }
const marketDetailsPost: MarketDetails = getMarketDetails( const marketDetailsPost: MarketDetails = getMarketDetails(inputReservesPost, outputReservesPost)
marketDetails.tradeType,
inputReservesPost,
outputReservesPost
)
const executionRate: Rate = calculateDecimalRate(outputAmount, inputAmount) as Rate const executionRate: Rate = calculateDecimalRate(outputAmount, inputAmount) as Rate
......
...@@ -3,16 +3,31 @@ ...@@ -3,16 +3,31 @@
"constant": true, "constant": true,
"inputs": [], "inputs": [],
"name": "decimals", "name": "decimals",
"outputs": [{ "name": "", "type": "uint8" }], "outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false, "payable": false,
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{ {
"constant": true, "constant": true,
"inputs": [{ "name": "", "type": "address" }], "inputs": [
{
"name": "",
"type": "address"
}
],
"name": "balanceOf", "name": "balanceOf",
"outputs": [{ "name": "", "type": "uint256" }], "outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false, "payable": false,
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
......
[
{
"name": "TokenPurchase",
"inputs": [
{ "type": "address", "name": "buyer", "indexed": true },
{ "type": "uint256", "name": "eth_sold", "indexed": true },
{ "type": "uint256", "name": "tokens_bought", "indexed": true }
],
"anonymous": false,
"type": "event"
},
{
"name": "EthPurchase",
"inputs": [
{ "type": "address", "name": "buyer", "indexed": true },
{ "type": "uint256", "name": "tokens_sold", "indexed": true },
{ "type": "uint256", "name": "eth_bought", "indexed": true }
],
"anonymous": false,
"type": "event"
},
{
"name": "AddLiquidity",
"inputs": [
{ "type": "address", "name": "provider", "indexed": true },
{ "type": "uint256", "name": "eth_amount", "indexed": true },
{ "type": "uint256", "name": "token_amount", "indexed": true }
],
"anonymous": false,
"type": "event"
},
{
"name": "RemoveLiquidity",
"inputs": [
{ "type": "address", "name": "provider", "indexed": true },
{ "type": "uint256", "name": "eth_amount", "indexed": true },
{ "type": "uint256", "name": "token_amount", "indexed": true }
],
"anonymous": false,
"type": "event"
},
{
"name": "Transfer",
"inputs": [
{ "type": "address", "name": "_from", "indexed": true },
{ "type": "address", "name": "_to", "indexed": true },
{ "type": "uint256", "name": "_value", "indexed": false }
],
"anonymous": false,
"type": "event"
},
{
"name": "Approval",
"inputs": [
{ "type": "address", "name": "_owner", "indexed": true },
{ "type": "address", "name": "_spender", "indexed": true },
{ "type": "uint256", "name": "_value", "indexed": false }
],
"anonymous": false,
"type": "event"
},
{
"name": "setup",
"outputs": [],
"inputs": [{ "type": "address", "name": "token_addr" }],
"constant": false,
"payable": false,
"type": "function",
"gas": 175875
},
{
"name": "addLiquidity",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "min_liquidity" },
{ "type": "uint256", "name": "max_tokens" },
{ "type": "uint256", "name": "deadline" }
],
"constant": false,
"payable": true,
"type": "function",
"gas": 82616
},
{
"name": "removeLiquidity",
"outputs": [{ "type": "uint256", "name": "out" }, { "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "amount" },
{ "type": "uint256", "name": "min_eth" },
{ "type": "uint256", "name": "min_tokens" },
{ "type": "uint256", "name": "deadline" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 116814
},
{ "name": "__default__", "outputs": [], "inputs": [], "constant": false, "payable": true, "type": "function" },
{
"name": "ethToTokenSwapInput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [{ "type": "uint256", "name": "min_tokens" }, { "type": "uint256", "name": "deadline" }],
"constant": false,
"payable": true,
"type": "function",
"gas": 12757
},
{
"name": "ethToTokenTransferInput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "min_tokens" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "recipient" }
],
"constant": false,
"payable": true,
"type": "function",
"gas": 12965
},
{
"name": "ethToTokenSwapOutput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [{ "type": "uint256", "name": "tokens_bought" }, { "type": "uint256", "name": "deadline" }],
"constant": false,
"payable": true,
"type": "function",
"gas": 50463
},
{
"name": "ethToTokenTransferOutput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_bought" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "recipient" }
],
"constant": false,
"payable": true,
"type": "function",
"gas": 50671
},
{
"name": "tokenToEthSwapInput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_sold" },
{ "type": "uint256", "name": "min_eth" },
{ "type": "uint256", "name": "deadline" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 47503
},
{
"name": "tokenToEthTransferInput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_sold" },
{ "type": "uint256", "name": "min_eth" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "recipient" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 47712
},
{
"name": "tokenToEthSwapOutput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "eth_bought" },
{ "type": "uint256", "name": "max_tokens" },
{ "type": "uint256", "name": "deadline" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 50175
},
{
"name": "tokenToEthTransferOutput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "eth_bought" },
{ "type": "uint256", "name": "max_tokens" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "recipient" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 50384
},
{
"name": "tokenToTokenSwapInput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_sold" },
{ "type": "uint256", "name": "min_tokens_bought" },
{ "type": "uint256", "name": "min_eth_bought" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "token_addr" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 51007
},
{
"name": "tokenToTokenTransferInput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_sold" },
{ "type": "uint256", "name": "min_tokens_bought" },
{ "type": "uint256", "name": "min_eth_bought" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "recipient" },
{ "type": "address", "name": "token_addr" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 51098
},
{
"name": "tokenToTokenSwapOutput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_bought" },
{ "type": "uint256", "name": "max_tokens_sold" },
{ "type": "uint256", "name": "max_eth_sold" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "token_addr" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 54928
},
{
"name": "tokenToTokenTransferOutput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_bought" },
{ "type": "uint256", "name": "max_tokens_sold" },
{ "type": "uint256", "name": "max_eth_sold" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "recipient" },
{ "type": "address", "name": "token_addr" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 55019
},
{
"name": "tokenToExchangeSwapInput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_sold" },
{ "type": "uint256", "name": "min_tokens_bought" },
{ "type": "uint256", "name": "min_eth_bought" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "exchange_addr" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 49342
},
{
"name": "tokenToExchangeTransferInput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_sold" },
{ "type": "uint256", "name": "min_tokens_bought" },
{ "type": "uint256", "name": "min_eth_bought" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "recipient" },
{ "type": "address", "name": "exchange_addr" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 49532
},
{
"name": "tokenToExchangeSwapOutput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_bought" },
{ "type": "uint256", "name": "max_tokens_sold" },
{ "type": "uint256", "name": "max_eth_sold" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "exchange_addr" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 53233
},
{
"name": "tokenToExchangeTransferOutput",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [
{ "type": "uint256", "name": "tokens_bought" },
{ "type": "uint256", "name": "max_tokens_sold" },
{ "type": "uint256", "name": "max_eth_sold" },
{ "type": "uint256", "name": "deadline" },
{ "type": "address", "name": "recipient" },
{ "type": "address", "name": "exchange_addr" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 53423
},
{
"name": "getEthToTokenInputPrice",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [{ "type": "uint256", "name": "eth_sold" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 5542
},
{
"name": "getEthToTokenOutputPrice",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [{ "type": "uint256", "name": "tokens_bought" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 6872
},
{
"name": "getTokenToEthInputPrice",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [{ "type": "uint256", "name": "tokens_sold" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 5637
},
{
"name": "getTokenToEthOutputPrice",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [{ "type": "uint256", "name": "eth_bought" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 6897
},
{
"name": "tokenAddress",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": 1413
},
{
"name": "factoryAddress",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": 1443
},
{
"name": "balanceOf",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [{ "type": "address", "name": "_owner" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 1645
},
{
"name": "transfer",
"outputs": [{ "type": "bool", "name": "out" }],
"inputs": [{ "type": "address", "name": "_to" }, { "type": "uint256", "name": "_value" }],
"constant": false,
"payable": false,
"type": "function",
"gas": 75034
},
{
"name": "transferFrom",
"outputs": [{ "type": "bool", "name": "out" }],
"inputs": [
{ "type": "address", "name": "_from" },
{ "type": "address", "name": "_to" },
{ "type": "uint256", "name": "_value" }
],
"constant": false,
"payable": false,
"type": "function",
"gas": 110907
},
{
"name": "approve",
"outputs": [{ "type": "bool", "name": "out" }],
"inputs": [{ "type": "address", "name": "_spender" }, { "type": "uint256", "name": "_value" }],
"constant": false,
"payable": false,
"type": "function",
"gas": 38769
},
{
"name": "allowance",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [{ "type": "address", "name": "_owner" }, { "type": "address", "name": "_spender" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 1925
},
{
"name": "name",
"outputs": [{ "type": "bytes32", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": 1623
},
{
"name": "symbol",
"outputs": [{ "type": "bytes32", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": 1653
},
{
"name": "decimals",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": 1683
},
{
"name": "totalSupply",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": 1713
}
]
[ [
{ {
"constant": true, "name": "NewExchange",
"inputs": [{ "name": "token", "type": "address" }], "inputs": [
{ "type": "address", "name": "token", "indexed": true },
{ "type": "address", "name": "exchange", "indexed": true }
],
"anonymous": false,
"type": "event"
},
{
"name": "initializeFactory",
"outputs": [],
"inputs": [{ "type": "address", "name": "template" }],
"constant": false,
"payable": false,
"type": "function",
"gas": 35725
},
{
"name": "createExchange",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [{ "type": "address", "name": "token" }],
"constant": false,
"payable": false,
"type": "function",
"gas": 187911
},
{
"name": "getExchange", "name": "getExchange",
"outputs": [{ "name": "", "type": "address" }], "outputs": [{ "type": "address", "name": "out" }],
"inputs": [{ "type": "address", "name": "token" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 715
},
{
"name": "getToken",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [{ "type": "address", "name": "exchange" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 745
},
{
"name": "getTokenWithId",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [{ "type": "uint256", "name": "token_id" }],
"constant": true,
"payable": false,
"type": "function",
"gas": 736
},
{
"name": "exchangeTemplate",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": 633
},
{
"name": "tokenCount",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false, "payable": false,
"stateMutability": "view", "type": "function",
"type": "function" "gas": 663
} }
] ]
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { Token } from '../types'
import ERC20 from './abis/ERC20.json' import ERC20 from './abis/ERC20.json'
import FACTORY from './abis/FACTORY.json' import FACTORY from './abis/FACTORY.json'
import EXCHANGE from './abis/EXCHANGE.json'
//// constants for internal and external use //// constants for internal and external use
export const ETH = 'ETH' export const ETH = 'ETH'
...@@ -21,9 +21,12 @@ export const FACTORY_ADDRESS: { [key: number]: string } = { ...@@ -21,9 +21,12 @@ export const FACTORY_ADDRESS: { [key: number]: string } = {
[SUPPORTED_CHAIN_ID.Kovan]: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30' [SUPPORTED_CHAIN_ID.Kovan]: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30'
} }
export const FACTORY_ABI: string = JSON.stringify(FACTORY)
export const EXCHANGE_ABI: string = JSON.stringify(EXCHANGE)
export enum TRADE_TYPE { export enum TRADE_TYPE {
TOKEN_TO_ETH = 'TOKEN_TO_ETH',
ETH_TO_TOKEN = 'ETH_TO_TOKEN', ETH_TO_TOKEN = 'ETH_TO_TOKEN',
TOKEN_TO_ETH = 'TOKEN_TO_ETH',
TOKEN_TO_TOKEN = 'TOKEN_TO_TOKEN' TOKEN_TO_TOKEN = 'TOKEN_TO_TOKEN'
} }
...@@ -32,6 +35,36 @@ export enum TRADE_EXACT { ...@@ -32,6 +35,36 @@ export enum TRADE_EXACT {
OUTPUT = 'OUTPUT' OUTPUT = 'OUTPUT'
} }
export enum TRADE_METHODS {
ethToTokenSwapInput = 'ethToTokenSwapInput',
ethToTokenTransferInput = 'ethToTokenTransferInput',
ethToTokenSwapOutput = 'ethToTokenSwapOutput',
ethToTokenTransferOutput = 'ethToTokenTransferOutput',
tokenToEthSwapInput = 'tokenToEthSwapInput',
tokenToEthTransferInput = 'tokenToEthTransferInput',
tokenToEthSwapOutput = 'tokenToEthSwapOutput',
tokenToEthTransferOutput = 'tokenToEthTransferOutput',
tokenToTokenSwapInput = 'tokenToTokenSwapInput',
tokenToTokenTransferInput = 'tokenToTokenTransferInput',
tokenToTokenSwapOutput = 'tokenToTokenSwapOutput',
tokenToTokenTransferOutput = 'tokenToTokenTransferOutput'
}
export const TRADE_METHOD_IDS: { [key: string]: string } = {
[TRADE_METHODS.ethToTokenSwapInput]: '0xf39b5b9b',
[TRADE_METHODS.ethToTokenTransferInput]: '0xad65d76d',
[TRADE_METHODS.ethToTokenSwapOutput]: '0x6b1d4db7',
[TRADE_METHODS.ethToTokenTransferOutput]: '0x0b573638',
[TRADE_METHODS.tokenToEthSwapInput]: '0x95e3c50b',
[TRADE_METHODS.tokenToEthTransferInput]: '0x7237e031',
[TRADE_METHODS.tokenToEthSwapOutput]: '0x013efd8b',
[TRADE_METHODS.tokenToEthTransferOutput]: '0xd4e4841d',
[TRADE_METHODS.tokenToTokenSwapInput]: '0xddf7e1a7',
[TRADE_METHODS.tokenToTokenTransferInput]: '0xf552d91b',
[TRADE_METHODS.tokenToTokenSwapOutput]: '0xb040d545',
[TRADE_METHODS.tokenToTokenTransferOutput]: '0xf3c0efe9'
}
export enum FIXED_UNDERFLOW_BEHAVIOR { export enum FIXED_UNDERFLOW_BEHAVIOR {
ZERO = 'ZERO', ZERO = 'ZERO',
LESS_THAN = 'LESS_THAN', LESS_THAN = 'LESS_THAN',
...@@ -39,9 +72,9 @@ export enum FIXED_UNDERFLOW_BEHAVIOR { ...@@ -39,9 +72,9 @@ export enum FIXED_UNDERFLOW_BEHAVIOR {
} }
//// constants for internal use //// constants for internal use
export const MAX_DECIMAL_PLACES = 18 export const _MAX_DECIMAL_PLACES = 18
export const ROUNDING_MODE = BigNumber.ROUND_HALF_UP export const _ROUNDING_MODE = BigNumber.ROUND_HALF_UP
BigNumber.set({ DECIMAL_PLACES: MAX_DECIMAL_PLACES, ROUNDING_MODE }) BigNumber.set({ DECIMAL_PLACES: _MAX_DECIMAL_PLACES, ROUNDING_MODE: _ROUNDING_MODE })
export const _0: BigNumber = new BigNumber('0') export const _0: BigNumber = new BigNumber('0')
export const _1: BigNumber = new BigNumber('1') export const _1: BigNumber = new BigNumber('1')
...@@ -49,23 +82,16 @@ export const _10: BigNumber = new BigNumber('10') ...@@ -49,23 +82,16 @@ export const _10: BigNumber = new BigNumber('10')
export const _997: BigNumber = new BigNumber('997') export const _997: BigNumber = new BigNumber('997')
export const _1000: BigNumber = new BigNumber('1000') export const _1000: BigNumber = new BigNumber('1000')
export const _10000: BigNumber = new BigNumber('10000') export const _10000: BigNumber = new BigNumber('10000')
export const MAX_UINT8: number = 2 ** 8 - 1 export const _MAX_UINT8 = 255
export const MAX_UINT256: BigNumber = new BigNumber('2').exponentiatedBy(new BigNumber('256')).minus(_1) export const _MAX_UINT256: BigNumber = new BigNumber(
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
)
export function ETH_TOKEN(chainId?: number): Token { export const _CHAIN_ID_NAME: { [key: number]: string } = {
return {
...(chainId ? { chainId } : {}),
address: ETH,
decimals: 18
}
}
export const CHAIN_ID_NAME: { [key: number]: string } = {
[SUPPORTED_CHAIN_ID.Mainnet]: 'homestead', [SUPPORTED_CHAIN_ID.Mainnet]: 'homestead',
[SUPPORTED_CHAIN_ID.Ropsten]: 'ropsten', [SUPPORTED_CHAIN_ID.Ropsten]: 'ropsten',
[SUPPORTED_CHAIN_ID.Rinkeby]: 'rinkeby', [SUPPORTED_CHAIN_ID.Rinkeby]: 'rinkeby',
[SUPPORTED_CHAIN_ID.Kovan]: 'kovan' [SUPPORTED_CHAIN_ID.Kovan]: 'kovan'
} }
export const ERC20_ABI: string = JSON.stringify(ERC20) export const _ERC20_ABI: string = JSON.stringify(ERC20)
export const FACTORY_ABI: string = JSON.stringify(FACTORY)
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { ETH, ETH_TOKEN, CHAIN_ID_NAME, ERC20_ABI, FACTORY_ADDRESS, FACTORY_ABI } from '../constants' import { ChainIdOrProvider, isChainId, Token, TokenReservesNormalized, _ChainIdAndProvider } from '../types'
import { ChainIdOrProvider, isChainId, _ChainIdAndProvider, Token, TokenReservesNormalized } from '../types' import { ETH, SUPPORTED_CHAIN_ID, FACTORY_ABI, FACTORY_ADDRESS, _CHAIN_ID_NAME, _ERC20_ABI } from '../constants'
import { normalizeAddress, normalizeBigNumberish } from '../_utils' import { normalizeBigNumberish, normalizeAddress } from '../_utils'
// get contract object with address, ABI, and provider function getContract(address: string, ABI: string, provider: ethers.providers.BaseProvider): ethers.Contract {
function _getContract(address: string, ABI: string, provider: ethers.providers.BaseProvider): ethers.Contract { return new ethers.Contract(address, ABI, provider)
return new ethers.Contract(normalizeAddress(address), ABI, provider)
} }
// get chain id and provider with either a chain id or a provider async function getChainIdAndProvider(chainIdOrProvider: ChainIdOrProvider): Promise<_ChainIdAndProvider> {
async function _getChainIdAndProvider(chainIdOrProvider: ChainIdOrProvider): Promise<_ChainIdAndProvider> {
// if a chainId is provided, get a default provider for it // if a chainId is provided, get a default provider for it
if (isChainId(chainIdOrProvider)) { if (isChainId(chainIdOrProvider)) {
return { return {
chainId: chainIdOrProvider, chainId: chainIdOrProvider,
provider: ethers.getDefaultProvider(CHAIN_ID_NAME[chainIdOrProvider]) provider: ethers.getDefaultProvider(_CHAIN_ID_NAME[chainIdOrProvider])
} }
} }
// if a provider is provided, fetch the chainId from it // if a provider is provided, fetch the chainId from it
else { else {
const { chainId }: ethers.utils.Network = await chainIdOrProvider.getNetwork() const { chainId }: ethers.utils.Network = await chainIdOrProvider.getNetwork()
if (!(chainId in SUPPORTED_CHAIN_ID)) {
throw Error(`chainId ${chainId} is not valid.`)
}
return { return {
chainId, chainId,
provider: chainIdOrProvider provider: chainIdOrProvider
...@@ -28,13 +31,21 @@ async function _getChainIdAndProvider(chainIdOrProvider: ChainIdOrProvider): Pro ...@@ -28,13 +31,21 @@ async function _getChainIdAndProvider(chainIdOrProvider: ChainIdOrProvider): Pro
} }
} }
// get token data from an address and chain id/provider export function getEthToken(chainId?: number): Token {
async function _getToken(tokenAddress: string, chainIdAndProvider: _ChainIdAndProvider): Promise<Token> { return {
...(chainId ? { chainId } : {}),
address: ETH,
decimals: 18
}
}
async function getToken(tokenAddress: string, chainIdAndProvider: _ChainIdAndProvider): Promise<Token> {
if (tokenAddress === ETH) { if (tokenAddress === ETH) {
return ETH_TOKEN(chainIdAndProvider.chainId) return getEthToken(chainIdAndProvider.chainId)
} else { } else {
const ERC20Contract: ethers.Contract = _getContract(tokenAddress, ERC20_ABI, chainIdAndProvider.provider) const ERC20Contract: ethers.Contract = getContract(tokenAddress, _ERC20_ABI, chainIdAndProvider.provider)
const decimals: number = await ERC20Contract.decimals() const decimals: number = await ERC20Contract.decimals()
return { return {
chainId: chainIdAndProvider.chainId, chainId: chainIdAndProvider.chainId,
address: ERC20Contract.address, address: ERC20Contract.address,
...@@ -43,28 +54,31 @@ async function _getToken(tokenAddress: string, chainIdAndProvider: _ChainIdAndPr ...@@ -43,28 +54,31 @@ async function _getToken(tokenAddress: string, chainIdAndProvider: _ChainIdAndPr
} }
} }
// external function to get token reserves
export async function getTokenReserves( export async function getTokenReserves(
tokenAddress: string, tokenAddress: string,
chainIdOrProvider: ChainIdOrProvider = 1 chainIdOrProvider: ChainIdOrProvider = 1
): Promise<TokenReservesNormalized> { ): Promise<TokenReservesNormalized> {
const chainIdAndProvider: _ChainIdAndProvider = await _getChainIdAndProvider(chainIdOrProvider) // validate input arguments
const normalizedTokenAddress: string = normalizeAddress(tokenAddress)
const chainIdAndProvider: _ChainIdAndProvider = await getChainIdAndProvider(chainIdOrProvider)
// fetch tokens // fetch tokens (async)
const ethTokenPromise: Promise<Token> = _getToken(ETH, chainIdAndProvider) const ethTokenPromise: Promise<Token> = getToken(ETH, chainIdAndProvider)
const tokenPromise: Promise<Token> = _getToken(tokenAddress, chainIdAndProvider) const tokenPromise: Promise<Token> = getToken(normalizedTokenAddress, chainIdAndProvider)
// get contracts // get contracts
const factoryContract: ethers.Contract = _getContract( const factoryContract: ethers.Contract = getContract(
FACTORY_ADDRESS[chainIdAndProvider.chainId], FACTORY_ADDRESS[chainIdAndProvider.chainId],
FACTORY_ABI, FACTORY_ABI,
chainIdAndProvider.provider chainIdAndProvider.provider
) )
const tokenContract: ethers.Contract = _getContract(tokenAddress, ERC20_ABI, chainIdAndProvider.provider) const tokenContract: ethers.Contract = getContract(normalizedTokenAddress, _ERC20_ABI, chainIdAndProvider.provider)
const exchangeAddress: string = await factoryContract.getExchange(tokenContract.address) // fetch exchange adddress (blocking async)
const exchangeAddress: string = await factoryContract.getExchange(normalizedTokenAddress)
const exchangeTokenPromise: Promise<Token> = _getToken(exchangeAddress, chainIdAndProvider) // fetch exchange token and eth/token balances (async)
const exchangeTokenPromise: Promise<Token> = getToken(exchangeAddress, chainIdAndProvider)
const ethBalancePromise: Promise<ethers.utils.BigNumber> = chainIdAndProvider.provider.getBalance(exchangeAddress) const ethBalancePromise: Promise<ethers.utils.BigNumber> = chainIdAndProvider.provider.getBalance(exchangeAddress)
const tokenBalancePromise: Promise<ethers.utils.BigNumber> = tokenContract.balanceOf(exchangeAddress) const tokenBalancePromise: Promise<ethers.utils.BigNumber> = tokenContract.balanceOf(exchangeAddress)
......
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { BigNumberish, FlexibleFormat, isFormat, FormatSignificantOptions, FormatFixedOptions } from '../types' import { BigNumberish, FlexibleFormat, isFormat, FormatSignificantOptions, FormatFixedOptions } from '../types'
import { _0, _10, MAX_DECIMAL_PLACES, ROUNDING_MODE, FIXED_UNDERFLOW_BEHAVIOR } from '../constants' import { _0, _10, _MAX_DECIMAL_PLACES, _ROUNDING_MODE, FIXED_UNDERFLOW_BEHAVIOR } from '../constants'
import { normalizeBigNumberish, ensureBoundedInteger, ensureAllUInt256, ensureAllUInt8 } from '../_utils' import { normalizeBigNumberish, ensureBoundedInteger, ensureAllUInt256, ensureAllUInt8 } from '../_utils'
function _format( function _format(
bigNumber: BigNumber, bigNumber: BigNumber,
decimalPlaces: number, decimalPlaces: number,
roundingMode: BigNumber.RoundingMode = ROUNDING_MODE, roundingMode: BigNumber.RoundingMode = _ROUNDING_MODE,
format: FlexibleFormat format: FlexibleFormat
): string { ): string {
return isFormat(format) || format return isFormat(format) || format
...@@ -17,11 +17,11 @@ function _format( ...@@ -17,11 +17,11 @@ function _format(
// bignumberish is converted to significantDigits, then cast back as a bignumber and formatted, dropping trailing 0s // bignumberish is converted to significantDigits, then cast back as a bignumber and formatted, dropping trailing 0s
export function formatSignificant(bigNumberish: BigNumberish, options?: FormatSignificantOptions): string { export function formatSignificant(bigNumberish: BigNumberish, options?: FormatSignificantOptions): string {
const { significantDigits = 6, roundingMode = ROUNDING_MODE, forceIntegerSignificance = true, format = false } = const { significantDigits = 6, roundingMode = _ROUNDING_MODE, forceIntegerSignificance = true, format = false } =
options || {} options || {}
const bigNumber: BigNumber = normalizeBigNumberish(bigNumberish) const bigNumber: BigNumber = normalizeBigNumberish(bigNumberish)
ensureBoundedInteger(significantDigits, [1, MAX_DECIMAL_PLACES]) ensureBoundedInteger(significantDigits, [1, _MAX_DECIMAL_PLACES])
const minimumSignificantDigits: number = forceIntegerSignificance ? bigNumber.integerValue().toFixed().length : 0 const minimumSignificantDigits: number = forceIntegerSignificance ? bigNumber.integerValue().toFixed().length : 0
const preciseBigNumber: BigNumber = new BigNumber( const preciseBigNumber: BigNumber = new BigNumber(
...@@ -34,14 +34,14 @@ export function formatSignificant(bigNumberish: BigNumberish, options?: FormatSi ...@@ -34,14 +34,14 @@ export function formatSignificant(bigNumberish: BigNumberish, options?: FormatSi
export function formatFixed(bigNumberish: BigNumberish, options?: FormatFixedOptions): string { export function formatFixed(bigNumberish: BigNumberish, options?: FormatFixedOptions): string {
const { const {
decimalPlaces = 4, decimalPlaces = 4,
roundingMode = ROUNDING_MODE, roundingMode = _ROUNDING_MODE,
dropTrailingZeros = true, dropTrailingZeros = true,
underflowBehavior = FIXED_UNDERFLOW_BEHAVIOR.ONE_DIGIT, underflowBehavior = FIXED_UNDERFLOW_BEHAVIOR.ONE_DIGIT,
format = false format = false
} = options || {} } = options || {}
const bigNumber: BigNumber = normalizeBigNumberish(bigNumberish) const bigNumber: BigNumber = normalizeBigNumberish(bigNumberish)
ensureBoundedInteger(decimalPlaces, MAX_DECIMAL_PLACES) ensureBoundedInteger(decimalPlaces, _MAX_DECIMAL_PLACES)
const minimumNonZeroValue: BigNumber = new BigNumber(decimalPlaces === 0 ? '0.5' : `0.${'0'.repeat(decimalPlaces)}5`) const minimumNonZeroValue: BigNumber = new BigNumber(decimalPlaces === 0 ? '0.5' : `0.${'0'.repeat(decimalPlaces)}5`)
if (bigNumber.isLessThan(minimumNonZeroValue)) { if (bigNumber.isLessThan(minimumNonZeroValue)) {
...@@ -75,8 +75,8 @@ function decimalize(bigNumberish: BigNumberish, decimals: number): BigNumber { ...@@ -75,8 +75,8 @@ function decimalize(bigNumberish: BigNumberish, decimals: number): BigNumber {
ensureAllUInt8([decimals]) ensureAllUInt8([decimals])
if (decimals > MAX_DECIMAL_PLACES) { if (decimals > _MAX_DECIMAL_PLACES) {
throw Error(`This function does not support decimals greater than ${MAX_DECIMAL_PLACES}.`) throw Error(`This function does not support decimals greater than ${_MAX_DECIMAL_PLACES}.`)
} }
return bigNumber.dividedBy(_10.exponentiatedBy(decimals)) return bigNumber.dividedBy(_10.exponentiatedBy(decimals))
......
...@@ -5,13 +5,17 @@ export { ...@@ -5,13 +5,17 @@ export {
ETH, ETH,
SUPPORTED_CHAIN_ID, SUPPORTED_CHAIN_ID,
FACTORY_ADDRESS, FACTORY_ADDRESS,
FACTORY_ABI,
EXCHANGE_ABI,
TRADE_TYPE, TRADE_TYPE,
TRADE_EXACT, TRADE_EXACT,
TRADE_METHODS,
TRADE_METHOD_IDS,
FIXED_UNDERFLOW_BEHAVIOR FIXED_UNDERFLOW_BEHAVIOR
} from './constants' } from './constants'
export * from './data' export * from './data'
export * from './computation' export * from './computation'
export * from './format' export * from './format'
export * from './orchestration' export * from './orchestration'
export * from './transact'
import { BigNumberish, ChainIdOrProvider, TokenReservesNormalized, MarketDetails, TradeDetails } from '../types' import {
BigNumberish,
import { TRADE_TYPE, TRADE_EXACT, ETH } from '../constants' ChainIdOrProvider,
TokenReservesNormalized,
OptionalReserves,
MarketDetails,
TradeDetails
} from '../types'
import { TRADE_EXACT } from '../constants'
import { getTokenReserves } from '../data' import { getTokenReserves } from '../data'
import { getMarketDetails, getTradeDetails } from '../computation' import { getMarketDetails, getTradeDetails } from '../computation'
export async function getTrade( //// eth for tokens
inputTokenAddress: string, export function tradeExactEthForTokensWithData(reserves: OptionalReserves, ethAmount: BigNumberish): TradeDetails {
outputTokenAddress: string, const marketDetails: MarketDetails = getMarketDetails(undefined, reserves)
tradeType: TRADE_TYPE, return getTradeDetails(TRADE_EXACT.INPUT, ethAmount, marketDetails)
tradeExact: TRADE_EXACT, }
tradeAmount: BigNumberish,
export async function tradeExactEthForTokens(
tokenAddress: string,
ethAmount: BigNumberish,
chainIdOrProvider?: ChainIdOrProvider
): Promise<TradeDetails> {
const tokenReserves: TokenReservesNormalized = await getTokenReserves(tokenAddress, chainIdOrProvider)
return tradeExactEthForTokensWithData(tokenReserves, ethAmount)
}
export function tradeEthForExactTokensWithData(reserves: OptionalReserves, tokenAmount: BigNumberish): TradeDetails {
const marketDetails: MarketDetails = getMarketDetails(undefined, reserves)
return getTradeDetails(TRADE_EXACT.OUTPUT, tokenAmount, marketDetails)
}
export async function tradeEthForExactTokens(
tokenAddress: string,
tokenAmount: BigNumberish,
chainIdOrProvider?: ChainIdOrProvider chainIdOrProvider?: ChainIdOrProvider
): Promise<TradeDetails> { ): Promise<TradeDetails> {
const tokenReservesInput: TokenReservesNormalized | null = const tokenReserves: TokenReservesNormalized = await getTokenReserves(tokenAddress, chainIdOrProvider)
inputTokenAddress === ETH ? null : await getTokenReserves(inputTokenAddress, chainIdOrProvider) return tradeEthForExactTokensWithData(tokenReserves, tokenAmount)
const tokenReservesOutput: TokenReservesNormalized | null = }
outputTokenAddress === ETH ? null : await getTokenReserves(outputTokenAddress, chainIdOrProvider)
//// tokens to eth
export function tradeExactTokensForEthWithData(reserves: OptionalReserves, tokenAmount: BigNumberish): TradeDetails {
const marketDetails: MarketDetails = getMarketDetails(reserves, undefined)
return getTradeDetails(TRADE_EXACT.INPUT, tokenAmount, marketDetails)
}
const marketDetails: MarketDetails = getMarketDetails(tradeType, tokenReservesInput, tokenReservesOutput) export async function tradeExactTokensForEth(
tokenAddress: string,
tokenAmount: BigNumberish,
chainIdOrProvider?: ChainIdOrProvider
): Promise<TradeDetails> {
const tokenReserves: TokenReservesNormalized = await getTokenReserves(tokenAddress, chainIdOrProvider)
return tradeExactTokensForEthWithData(tokenReserves, tokenAmount)
}
return getTradeDetails(tradeExact, tradeAmount, marketDetails) export function tradeTokensForExactEthWithData(reserves: OptionalReserves, ethAmount: BigNumberish): TradeDetails {
const marketDetails: MarketDetails = getMarketDetails(reserves, undefined)
return getTradeDetails(TRADE_EXACT.OUTPUT, ethAmount, marketDetails)
}
export async function tradeTokensForExactEth(
tokenAddress: string,
ethAmount: BigNumberish,
chainIdOrProvider?: ChainIdOrProvider
): Promise<TradeDetails> {
const tokenReserves: TokenReservesNormalized = await getTokenReserves(tokenAddress, chainIdOrProvider)
return tradeTokensForExactEthWithData(tokenReserves, ethAmount)
}
//// tokens for tokens
export function tradeExactTokensForTokensWithData(
reservesInput: OptionalReserves,
reservesOutput: OptionalReserves,
tokenAmount: BigNumberish
): TradeDetails {
const marketDetails: MarketDetails = getMarketDetails(reservesInput, reservesOutput)
return getTradeDetails(TRADE_EXACT.INPUT, tokenAmount, marketDetails)
}
export async function tradeExactTokensForTokens(
tokenAddressInput: string,
tokenAddressOutput: string,
tokenAmount: BigNumberish,
chainIdOrProvider?: ChainIdOrProvider
): Promise<TradeDetails> {
const tokenReservesInput: TokenReservesNormalized = await getTokenReserves(tokenAddressInput, chainIdOrProvider)
const tokenReservesOutput: TokenReservesNormalized = await getTokenReserves(tokenAddressOutput, chainIdOrProvider)
return tradeExactTokensForTokensWithData(tokenReservesInput, tokenReservesOutput, tokenAmount)
}
export function tradeTokensForExactTokensWithData(
reservesInput: OptionalReserves,
reservesOutput: OptionalReserves,
tokenAmount: BigNumberish
): TradeDetails {
const marketDetails: MarketDetails = getMarketDetails(reservesInput, reservesOutput)
return getTradeDetails(TRADE_EXACT.OUTPUT, tokenAmount, marketDetails)
}
export async function tradeTokensForExactTokens(
tokenAddressInput: string,
tokenAddressOutput: string,
tokenAmount: BigNumberish,
chainIdOrProvider?: ChainIdOrProvider
): Promise<TradeDetails> {
const tokenReservesInput: TokenReservesNormalized = await getTokenReserves(tokenAddressInput, chainIdOrProvider)
const tokenReservesOutput: TokenReservesNormalized = await getTokenReserves(tokenAddressOutput, chainIdOrProvider)
return tradeTokensForExactTokensWithData(tokenReservesInput, tokenReservesOutput, tokenAmount)
} }
import BigNumber from 'bignumber.js'
import {
TokenReservesNormalized,
TradeDetails,
MethodArgument,
ExecutionDetails,
_SlippageBounds,
_PartialExecutionDetails
} from '../types'
import { _0, _1, _10000, _MAX_UINT256, TRADE_TYPE, TRADE_EXACT, TRADE_METHODS, TRADE_METHOD_IDS } from '../constants'
import { normalizeAddress } from '../_utils'
function removeUndefined(methodArguments: (MethodArgument | undefined)[]): MethodArgument[] {
return methodArguments.filter((a: MethodArgument | undefined): boolean => a !== undefined) as MethodArgument[]
}
function calculateSlippage(value: BigNumber, maxSlippage: number): _SlippageBounds {
const offset: BigNumber = value.multipliedBy(maxSlippage).dividedBy(_10000)
const minimum: BigNumber = value.minus(offset).integerValue(BigNumber.ROUND_FLOOR)
const maximum: BigNumber = value.plus(offset).integerValue(BigNumber.ROUND_CEIL)
return {
minimum: minimum.isLessThan(_0) ? _0 : minimum,
maximum: maximum.isGreaterThan(_MAX_UINT256) ? _MAX_UINT256 : maximum
}
}
function getReserves(trade: TradeDetails): TokenReservesNormalized {
switch (trade.tradeType) {
case TRADE_TYPE.ETH_TO_TOKEN: {
return trade.marketDetailsPre.outputReserves as TokenReservesNormalized
}
case TRADE_TYPE.TOKEN_TO_ETH: {
return trade.marketDetailsPre.inputReserves as TokenReservesNormalized
}
case TRADE_TYPE.TOKEN_TO_TOKEN: {
return trade.marketDetailsPre.inputReserves as TokenReservesNormalized
}
default: {
throw Error(`tradeType ${trade.tradeType} is invalid.`)
}
}
}
function getMethodName(trade: TradeDetails, transfer: boolean = false): string {
switch (trade.tradeType) {
case TRADE_TYPE.ETH_TO_TOKEN: {
if (trade.tradeExact === TRADE_EXACT.INPUT && !transfer) {
return TRADE_METHODS.ethToTokenSwapInput
} else if (trade.tradeExact === TRADE_EXACT.INPUT && transfer) {
return TRADE_METHODS.ethToTokenTransferInput
} else if (trade.tradeExact === TRADE_EXACT.OUTPUT && !transfer) {
return TRADE_METHODS.ethToTokenSwapOutput
} else {
return TRADE_METHODS.ethToTokenTransferOutput
}
}
case TRADE_TYPE.TOKEN_TO_ETH: {
if (trade.tradeExact === TRADE_EXACT.INPUT && !transfer) {
return TRADE_METHODS.tokenToEthSwapInput
} else if (trade.tradeExact === TRADE_EXACT.INPUT && transfer) {
return TRADE_METHODS.tokenToEthTransferInput
} else if (trade.tradeExact === TRADE_EXACT.OUTPUT && !transfer) {
return TRADE_METHODS.tokenToEthSwapOutput
} else {
return TRADE_METHODS.tokenToEthTransferOutput
}
}
case TRADE_TYPE.TOKEN_TO_TOKEN: {
if (trade.tradeExact === TRADE_EXACT.INPUT && !transfer) {
return TRADE_METHODS.tokenToTokenSwapInput
} else if (trade.tradeExact === TRADE_EXACT.INPUT && transfer) {
return TRADE_METHODS.tokenToTokenTransferInput
} else if (trade.tradeExact === TRADE_EXACT.OUTPUT && !transfer) {
return TRADE_METHODS.tokenToTokenSwapOutput
} else {
return TRADE_METHODS.tokenToTokenTransferOutput
}
}
default: {
throw Error(`tradeType ${trade.tradeType} is invalid.`)
}
}
}
function getValueAndMethodArguments(
trade: TradeDetails,
methodName: string,
maxSlippage: number,
deadline: number,
recipient?: string
): _PartialExecutionDetails {
switch (methodName) {
case TRADE_METHODS.ethToTokenSwapInput:
case TRADE_METHODS.ethToTokenTransferInput: {
return {
value: trade.inputAmount.amount,
methodArguments: removeUndefined([
calculateSlippage(trade.outputAmount.amount, maxSlippage).minimum,
deadline,
recipient
])
}
}
case TRADE_METHODS.ethToTokenSwapOutput:
case TRADE_METHODS.ethToTokenTransferOutput: {
return {
value: calculateSlippage(trade.inputAmount.amount, maxSlippage).maximum,
methodArguments: removeUndefined([trade.outputAmount.amount, deadline, recipient])
}
}
case TRADE_METHODS.tokenToEthSwapInput:
case TRADE_METHODS.tokenToEthTransferInput: {
return {
value: _0,
methodArguments: removeUndefined([
trade.inputAmount.amount,
calculateSlippage(trade.outputAmount.amount, maxSlippage).minimum,
deadline,
recipient
])
}
}
case TRADE_METHODS.tokenToEthSwapOutput:
case TRADE_METHODS.tokenToEthTransferOutput: {
return {
value: _0,
methodArguments: removeUndefined([
trade.outputAmount.amount,
calculateSlippage(trade.inputAmount.amount, maxSlippage).maximum,
deadline,
recipient
])
}
}
case TRADE_METHODS.tokenToTokenSwapInput:
case TRADE_METHODS.tokenToTokenTransferInput: {
if (!trade.outputAmount.token.address) {
throw Error('trade does not include output token address.')
}
return {
value: _0,
methodArguments: removeUndefined([
trade.inputAmount.amount,
calculateSlippage(trade.outputAmount.amount, maxSlippage).minimum,
_1,
deadline,
recipient,
trade.outputAmount.token.address
])
}
}
case TRADE_METHODS.tokenToTokenSwapOutput:
case TRADE_METHODS.tokenToTokenTransferOutput: {
if (!trade.outputAmount.token.address) {
throw Error('trade does not include output token address.')
}
return {
value: _0,
methodArguments: removeUndefined([
trade.outputAmount.amount,
calculateSlippage(trade.inputAmount.amount, maxSlippage).maximum,
_MAX_UINT256,
deadline,
recipient,
trade.outputAmount.token.address
])
}
}
default: {
throw Error(`methodName ${methodName} is invalid.`)
}
}
}
export function getExecutionDetails(
trade: TradeDetails,
maxSlippage?: number,
deadline?: number,
recipient?: string
): ExecutionDetails {
const reserves: TokenReservesNormalized = getReserves(trade)
if (!reserves.exchange || !reserves.exchange.address) {
throw Error('trade does not include exchange address.')
}
const methodName: string = getMethodName(trade, !!recipient)
const methodId: string = TRADE_METHOD_IDS[methodName]
const { value, methodArguments }: _PartialExecutionDetails = getValueAndMethodArguments(
trade,
methodName,
maxSlippage || 200,
deadline || Math.round(Date.now() / 1000 + 60 * 10),
recipient && normalizeAddress(recipient)
)
return {
exchangeAddress: reserves.exchange.address,
methodName,
methodId,
value,
methodArguments: methodArguments
}
}
...@@ -49,18 +49,22 @@ export interface EthReserves { ...@@ -49,18 +49,22 @@ export interface EthReserves {
} }
// type for input data // type for input data
export type OptionalReserves = TokenReserves | EthReserves | undefined | null export type OptionalReserves = TokenReserves | EthReserves | undefined
// type guard for OptionalReserves // type guard for OptionalReserves
export function areTokenReserves(reserves: OptionalReserves): reserves is TokenReserves { export function areTokenReserves(reserves: OptionalReserves): reserves is TokenReserves {
const tokenReserves: TokenReserves = reserves as TokenReserves const tokenReserves: TokenReserves = reserves as TokenReserves
return !!tokenReserves && tokenReserves.ethReserve !== undefined && tokenReserves.tokenReserve !== undefined return (
tokenReserves !== undefined && tokenReserves.ethReserve !== undefined && tokenReserves.tokenReserve !== undefined
)
} }
// type guard for OptionalReserves // type guard for OptionalReserves
export function areETHReserves(reserves: OptionalReserves): reserves is TokenReserves { export function areETHReserves(reserves: OptionalReserves): reserves is EthReserves {
const tokenReserves: TokenReserves = reserves as TokenReserves const tokenReserves: TokenReserves = reserves as TokenReserves
return !!tokenReserves && tokenReserves.ethReserve !== undefined && tokenReserves.tokenReserve !== undefined return (
tokenReserves !== undefined && tokenReserves.ethReserve === undefined && tokenReserves.tokenReserve === undefined
)
} }
// type for output data // type for output data
...@@ -97,6 +101,16 @@ export interface TradeDetails { ...@@ -97,6 +101,16 @@ export interface TradeDetails {
executionRateSlippage: BigNumber executionRateSlippage: BigNumber
} }
export type MethodArgument = BigNumber | number | string
export interface ExecutionDetails {
exchangeAddress: string
methodName: string
methodId: string
value: BigNumber
methodArguments: MethodArgument[]
}
//// types for formatting data //// types for formatting data
export type FlexibleFormat = BigNumber.Format | boolean export type FlexibleFormat = BigNumber.Format | boolean
...@@ -147,3 +161,13 @@ export interface _PartialTradeDetails { ...@@ -147,3 +161,13 @@ export interface _PartialTradeDetails {
inputReservesPost: NormalizedReserves inputReservesPost: NormalizedReserves
outputReservesPost: NormalizedReserves outputReservesPost: NormalizedReserves
} }
export interface _SlippageBounds {
minimum: BigNumber
maximum: BigNumber
}
export interface _PartialExecutionDetails {
value: BigNumber
methodArguments: MethodArgument[]
}
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
"sourceMap": true /* Generates corresponding '.map' file. */, "sourceMap": true /* Generates corresponding '.map' file. */,
// "outFile": "./", /* Concatenate and emit output to single file. */ // "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist" /* Redirect output structure to the directory. */, "outDir": "./dist" /* Redirect output structure to the directory. */,
// "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */ // "composite": true, /* Enable project compilation */
// "incremental": true, /* Enable incremental compilation */ // "incremental": true, /* Enable incremental compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
......
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