Commit a56aa056 authored by Noah Zinsmeister's avatar Noah Zinsmeister

commit initial framework

parents
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"extends": ["plugin:@typescript-eslint/recommended", "prettier", "prettier/@typescript-eslint"]
}
dist/
\ No newline at end of file
{
"semi": false,
"singleQuote": true,
"printWidth": 120
}
language: node_js
node_js:
- '10'
cache:
directories:
- node_modules
install: yarn
script:
- yarn check:all
- yarn test
- yarn build
after_script:
- yarn coverage
This diff is collapsed.
# Uniswap SDK
[![Build Status](https://travis-ci.org/Uniswap/uniswap-sdk.svg?branch=master)](https://travis-ci.org/Uniswap/uniswap-sdk)
[![Coverage Status](https://coveralls.io/repos/github/Uniswap/uniswap-sdk/badge.svg?branch=master)](https://coveralls.io/github/Uniswap/uniswap-sdk?branch=master)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node'
}
{
"name": "@uniswap/sdk",
"version": "1.0.0-beta.0",
"description": "An SDK for building applications on top of the Uniswap exchange protocol.",
"keywords": [
"Uniswap",
"Ethereum",
"SDK"
],
"main": "./dist/index.js",
"scripts": {
"lint:base": "yarn eslint './src/**/*.ts'",
"format:base": "yarn prettier './src/**/*.ts'",
"fix:lint": "yarn lint:base --fix",
"fix:format": "yarn format:base --write",
"fix:all": "yarn fix:lint && yarn fix:format",
"check:lint": "yarn lint:base",
"check:format": "yarn format:base --check",
"check:all": "yarn check:lint && yarn check:format",
"prebuild": "rm -rf dist/",
"build": "tsc",
"watch": "yarn prebuild && tsc --watch",
"test": "jest",
"coverage": "jest --coverage --coverageReporters=text-lcov | coveralls"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Uniswap/uniswap-sdk.git"
},
"author": "Noah Zinsmeister <noahwz@gmail.com>",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@types/jest": "^24.0.13",
"@typescript-eslint/eslint-plugin": "^1.9.0",
"@typescript-eslint/parser": "^1.9.0",
"coveralls": "^3.0.3",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.3.0",
"jest": "^24.8.0",
"prettier": "^1.17.1",
"ts-jest": "^24.0.2",
"typescript": "^3.4.5"
},
"dependencies": {
"bignumber.js": "^9.0.0",
"ethers": "^4.0.28"
}
}
import BigNumber from 'bignumber.js'
import { BigNumberish, TokenReserves } from '../types'
import { TRADE_TYPE } from '../constants'
import { getMarketRate } from '../computation'
function constructTokenReserves(
decimals: number,
ethReserveAmount: BigNumberish,
tokenReserveAmount: BigNumberish
): TokenReserves {
return {
token: { decimals },
ethReserve: { token: { decimals: 18 }, amount: ethReserveAmount },
tokenReserve: { token: { decimals }, amount: tokenReserveAmount }
}
}
function testMarketRates(
inputTokenReserves: TokenReserves | null,
outputTokenReserves: TokenReserves | null,
tradeType: TRADE_TYPE,
expectedMarketRate: string,
expectedMarketRateInverted: string
): void {
describe('regular', (): void => {
test('not inverted', (): void => {
const marketRate: BigNumber = getMarketRate(inputTokenReserves, outputTokenReserves, tradeType, false)
expect(marketRate.toFixed(18)).toBe(expectedMarketRate)
})
test('inverted', (): void => {
const marketRateInverted: BigNumber = getMarketRate(inputTokenReserves, outputTokenReserves, tradeType, true)
expect(marketRateInverted.toFixed(18)).toBe(expectedMarketRateInverted)
})
})
describe('manually inverted', (): void => {
const tradeTypeInverted =
tradeType === TRADE_TYPE.TOKEN_TO_TOKEN
? TRADE_TYPE.TOKEN_TO_TOKEN
: tradeType === TRADE_TYPE.ETH_TO_TOKEN
? TRADE_TYPE.TOKEN_TO_ETH
: TRADE_TYPE.ETH_TO_TOKEN
test('not inverted', (): void => {
const manuallyInvertedMarketRate: BigNumber = getMarketRate(
outputTokenReserves,
inputTokenReserves,
tradeTypeInverted,
false
)
expect(manuallyInvertedMarketRate.toFixed(18)).toBe(expectedMarketRateInverted)
})
test('inverted', (): void => {
const manuallyInvertedInvertedMarketRate: BigNumber = getMarketRate(
outputTokenReserves,
inputTokenReserves,
tradeTypeInverted,
true
)
expect(manuallyInvertedInvertedMarketRate.toFixed(18)).toBe(expectedMarketRate)
})
})
}
describe('getMarketRate', (): void => {
describe('dummy ETH/DAI and DAI/ETH', (): void => {
const tokenReserves: TokenReserves = constructTokenReserves(
18,
'4039700561005906883487',
'1094055210563660633471343'
)
const expectedMarketRate = '0.003692410147130181'
const expectedMarketRateInverted = '270.825818409480102284'
testMarketRates(null, tokenReserves, TRADE_TYPE.ETH_TO_TOKEN, expectedMarketRate, expectedMarketRateInverted)
testMarketRates(tokenReserves, null, TRADE_TYPE.TOKEN_TO_ETH, expectedMarketRateInverted, expectedMarketRate)
})
describe('dummy ETH/USDC and USDC/ETH', (): void => {
const tokenReserves: TokenReserves = constructTokenReserves(6, '1076592291503763426634', '292657693901')
const expectedMarketRate = '0.003678674143683891'
const expectedMarketRateInverted = '271.837069808684359442'
testMarketRates(null, tokenReserves, TRADE_TYPE.ETH_TO_TOKEN, expectedMarketRate, expectedMarketRateInverted)
testMarketRates(tokenReserves, null, TRADE_TYPE.TOKEN_TO_ETH, expectedMarketRateInverted, expectedMarketRate)
})
describe('dummy DAI/USDC and USDC/DAI', (): void => {
const DAITokenReserves: TokenReserves = constructTokenReserves(
18,
'4039700561005906883487',
'1094055210563660633471343'
)
const USDCTokenReserves: TokenReserves = constructTokenReserves(6, '1076592291503763426634', '292657693901')
const expectedMarketRate = '0.996279935624983178'
const expectedMarketRateInverted = '1.003733954927721499'
testMarketRates(
DAITokenReserves,
USDCTokenReserves,
TRADE_TYPE.TOKEN_TO_TOKEN,
expectedMarketRate,
expectedMarketRateInverted
)
testMarketRates(
USDCTokenReserves,
DAITokenReserves,
TRADE_TYPE.TOKEN_TO_TOKEN,
expectedMarketRateInverted,
expectedMarketRate
)
})
})
import { TokenReserves, Token } from '../types'
import { ETH as _ETH } from '../constants'
import { getTokenReserves } from '../data'
const ETH: Token = {
chainId: 1,
address: _ETH,
decimals: 18
}
const DAI: Token = {
chainId: 1,
address: '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
decimals: 18
}
const DAI_EXCHANGE: Token = {
chainId: 1,
address: '0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14',
decimals: 18
}
describe('getTokenReserves', (): void => {
test('DAI', async (done: jest.DoneCallback): Promise<void> => {
jest.setTimeout(20000) // 20 seconds
const tokenReserves: TokenReserves = await getTokenReserves(DAI.address as string)
expect(tokenReserves.token).toEqual(DAI)
expect(tokenReserves.exchange).toEqual(DAI_EXCHANGE)
expect(tokenReserves.ethReserve.token).toEqual(ETH)
expect(tokenReserves.ethReserve.amount).toBeTruthy()
expect(tokenReserves.tokenReserve.token).toEqual(DAI)
expect(tokenReserves.tokenReserve.amount).toBeTruthy()
done()
})
})
import { FlexibleFormat, FormatSignificantOptions, FormatFixedOptions } from '../types'
import { formatSignificant, formatFixed } from '../format'
import { FIXED_UNDERFLOW_BEHAVIOR } from '../constants'
function constructFormatSignificantOptions(
significantDigits: number,
forceIntegerSignificance: boolean = false,
format: FlexibleFormat = false
): FormatSignificantOptions {
return {
significantDigits,
forceIntegerSignificance,
format
}
}
function constructFormatFixedOptions(
decimalPlaces: number,
dropTrailingZeros: boolean = true,
format: FlexibleFormat = false,
underflowBehavior: FIXED_UNDERFLOW_BEHAVIOR = FIXED_UNDERFLOW_BEHAVIOR.ONE_DIGIT
): FormatFixedOptions {
return {
decimalPlaces,
dropTrailingZeros,
format,
underflowBehavior
}
}
describe('formatSignificant', (): void => {
test('regular', (): void => {
const formatted = formatSignificant('1.234', constructFormatSignificantOptions(2))
expect(formatted).toBe('1.2')
})
})
describe('formatFixed', (): void => {
test('regular', (): void => {
const formatted = formatFixed('1.234', constructFormatFixedOptions(1))
expect(formatted).toBe('1.2')
})
})
import BigNumber from 'bignumber.js'
import { BigNumberish } from '../../types'
import { normalizeBigNumberish } from '../../utils'
import { ethers } from 'ethers'
interface TestCase {
input: BigNumberish
expectedOutput: BigNumber
}
function constructTestCase(input: BigNumberish, expectedOutput: BigNumber): TestCase {
return { input, expectedOutput }
}
function testSuccesses(expectedSuccesses: TestCase[]): void {
test('failures', (): void => {
expectedSuccesses.forEach(
({ input, expectedOutput }: TestCase): void => {
const output: BigNumber = normalizeBigNumberish(input)
expect(output.isEqualTo(expectedOutput)).toBe(true)
}
)
})
}
function testFailures(expectedFailures: BigNumberish[]): void {
test('failures', (): void => {
expectedFailures.forEach(
(expectedFailure: BigNumberish): void => {
expect(
(): void => {
normalizeBigNumberish(expectedFailure)
}
).toThrow()
}
)
})
}
describe('normalizeBigNumberish', (): void => {
describe('string', (): void => {
const expectedSuccesses: TestCase[] = [
constructTestCase('0', new BigNumber('0')),
constructTestCase('1', new BigNumber('1')),
constructTestCase('1.234', new BigNumber('1.234'))
]
const expectedFailures: string[] = ['.', ',', 'a', '0.0.']
testSuccesses(expectedSuccesses)
testFailures(expectedFailures)
})
describe('number', (): void => {
const expectedSuccesses: TestCase[] = [
constructTestCase(0, new BigNumber(0)),
constructTestCase(1, new BigNumber(1)),
constructTestCase(1.234, new BigNumber(1.234))
]
const expectedFailures: number[] = [NaN, Infinity]
testSuccesses(expectedSuccesses)
testFailures(expectedFailures)
})
describe('BigNumber', (): void => {
const expectedSuccesses: TestCase[] = [
constructTestCase(new BigNumber(0), new BigNumber(0)),
constructTestCase(new BigNumber(1), new BigNumber(1)),
constructTestCase(new BigNumber('1.234'), new BigNumber('1.234'))
]
const expectedFailures: BigNumber[] = [new BigNumber(NaN)]
testSuccesses(expectedSuccesses)
testFailures(expectedFailures)
})
describe('ethers.utils.BigNumber', (): void => {
const expectedSuccesses: TestCase[] = [
constructTestCase(ethers.constants.Zero, new BigNumber(0)),
constructTestCase(ethers.constants.One, new BigNumber(1)),
constructTestCase(ethers.utils.bigNumberify('1234'), new BigNumber('1234')),
constructTestCase(ethers.utils.parseUnits('1.234', 3), new BigNumber('1234'))
]
const expectedFailures: ethers.utils.BigNumber[] = []
testSuccesses(expectedSuccesses)
testFailures(expectedFailures)
})
})
import BigNumber from 'bignumber.js'
import { TokenAmount, TokenReservesOptional } from './types'
import { _0, _1, _10, _997, _1000, TRADE_TYPE } from './constants'
import { ensureAllUInt8, ensureAllUInt256, normalizeBigNumberish } from './utils'
export function getInputPrice(inputAmount: BigNumber, inputReserve: BigNumber, outputReserve: BigNumber): BigNumber {
ensureAllUInt256([inputAmount, inputReserve, outputReserve])
if (inputReserve.isLessThanOrEqualTo(_0) || outputReserve.isLessThanOrEqualTo(_0)) {
throw Error(`Both inputReserve '${inputReserve}' and outputReserve '${outputReserve}' must be non-zero.`)
}
const inputAmountWithFee: BigNumber = inputAmount.multipliedBy(_997)
const numerator: BigNumber = inputAmountWithFee.multipliedBy(outputReserve)
const denominator: BigNumber = inputReserve.multipliedBy(_1000).plus(inputAmountWithFee)
const outputAmount = numerator.dividedToIntegerBy(denominator)
ensureAllUInt256([inputAmountWithFee, numerator, denominator, outputAmount])
return outputAmount
}
export function getOutputPrice(outputAmount: BigNumber, inputReserve: BigNumber, outputReserve: BigNumber): BigNumber {
ensureAllUInt256([outputAmount, inputReserve, outputReserve])
if (inputReserve.isLessThanOrEqualTo(_0) || outputReserve.isLessThanOrEqualTo(_0)) {
throw Error(`Both inputReserve '${inputReserve}' and outputReserve '${outputReserve}' must be non-zero.`)
}
const numerator: BigNumber = inputReserve.multipliedBy(outputAmount).multipliedBy(_1000)
const denominator: BigNumber = outputReserve.minus(outputAmount).multipliedBy(_997)
const inputAmount: BigNumber = numerator.dividedToIntegerBy(denominator).plus(_1)
ensureAllUInt256([numerator, denominator, inputAmount])
return inputAmount
}
function _getMarketRate(tokenReserves: TokenReservesOptional, tradeType: TRADE_TYPE, invert: boolean): BigNumber {
if (tokenReserves === null) {
throw Error('outputTokenReserves must be non-null.')
} else {
const numerator: TokenAmount =
tradeType === TRADE_TYPE.ETH_TO_TOKEN ? tokenReserves.ethReserve : tokenReserves.tokenReserve
const denominator: TokenAmount =
tradeType === TRADE_TYPE.ETH_TO_TOKEN ? tokenReserves.tokenReserve : tokenReserves.ethReserve
const numeratorAmount: BigNumber = normalizeBigNumberish(numerator.amount)
const denominatorAmount: BigNumber = normalizeBigNumberish(denominator.amount)
ensureAllUInt256([numeratorAmount, denominatorAmount])
const numeratorDecimals: number = numerator.token.decimals
const denominatorDecimals: number = denominator.token.decimals
ensureAllUInt8([numeratorDecimals, denominatorDecimals])
if (!invert) {
const decimalScalar = _10.exponentiatedBy(denominatorDecimals - numeratorDecimals)
return numeratorAmount.multipliedBy(decimalScalar).div(denominatorAmount)
} else {
const decimalScalar = _10.exponentiatedBy(numeratorDecimals - denominatorDecimals)
return denominatorAmount.multipliedBy(decimalScalar).div(numeratorAmount)
}
}
}
export function getMarketRate(
inputTokenReserves: TokenReservesOptional,
outputTokenReserves: TokenReservesOptional,
tradeType: TRADE_TYPE,
invert: boolean = false
): BigNumber {
if (tradeType === TRADE_TYPE.TOKEN_TO_TOKEN) {
if (inputTokenReserves === null || outputTokenReserves === null) {
throw Error('Both inputTokenReserves and outputTokenReserves must be non-null.')
} else {
const inputMarketRate: BigNumber = _getMarketRate(inputTokenReserves, TRADE_TYPE.TOKEN_TO_ETH, invert)
const outputMarketRate: BigNumber = _getMarketRate(outputTokenReserves, TRADE_TYPE.ETH_TO_TOKEN, invert)
return inputMarketRate.multipliedBy(outputMarketRate)
}
} else {
return _getMarketRate(
tradeType === TRADE_TYPE.ETH_TO_TOKEN ? outputTokenReserves : inputTokenReserves,
tradeType,
invert
)
}
}
[
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{ "name": "", "type": "uint8" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
[
{
"constant": true,
"inputs": [{ "name": "token", "type": "address" }],
"name": "getExchange",
"outputs": [{ "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
import BigNumber from 'bignumber.js'
import ERC20 from './abis/ERC20.json'
import FACTORY from './abis/FACTORY.json'
//// constants for internal and external use
export const ETH = 'ETH'
export enum SUPPORTED_CHAIN_ID {
Mainnet = 1,
Ropsten = 3,
Rinkeby = 4,
Kovan = 42
}
export const FACTORY_ADDRESS: { [key: number]: string } = {
[SUPPORTED_CHAIN_ID.Mainnet]: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
[SUPPORTED_CHAIN_ID.Ropsten]: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351',
[SUPPORTED_CHAIN_ID.Rinkeby]: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
[SUPPORTED_CHAIN_ID.Kovan]: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30'
}
export enum TRADE_TYPE {
TOKEN_TO_ETH = 'TOKEN_TO_ETH',
ETH_TO_TOKEN = 'ETH_TO_TOKEN',
TOKEN_TO_TOKEN = 'TOKEN_TO_TOKEN'
}
export enum TRADE_EXACT {
INPUT = 'INPUT',
OUTPUT = 'OUTPUT'
}
export enum FIXED_UNDERFLOW_BEHAVIOR {
ZERO = 'ZERO',
LESS_THAN = 'LESS_THAN',
ONE_DIGIT = 'ONE_DIGIT',
MAX_DECIMAL_PLACES = 'MAX_DECIMAL_PLACES'
}
//// constants for internal use
export const MAX_DECIMAL_PLACES = 18
export const ROUNDING_MODE = BigNumber.ROUND_HALF_UP
BigNumber.set({ DECIMAL_PLACES: MAX_DECIMAL_PLACES, ROUNDING_MODE })
export const _0: BigNumber = new BigNumber('0')
export const _1: BigNumber = new BigNumber('1')
export const _10: BigNumber = new BigNumber('10')
export const _997: BigNumber = new BigNumber('997')
export const _1000: BigNumber = new BigNumber('1000')
export const MAX_UINT8: number = 2 ** 8 - 1
export const MAX_UINT256: BigNumber = new BigNumber('2').exponentiatedBy(new BigNumber('256')).minus(_1)
export const ERC20_ABI: string = JSON.stringify(ERC20)
export const FACTORY_ABI: string = JSON.stringify(FACTORY)
export const CHAIN_ID_NAME: { [key: number]: string } = {
[SUPPORTED_CHAIN_ID.Mainnet]: 'homestead',
[SUPPORTED_CHAIN_ID.Ropsten]: 'ropsten',
[SUPPORTED_CHAIN_ID.Rinkeby]: 'rinkeby',
[SUPPORTED_CHAIN_ID.Kovan]: 'kovan'
}
import { ethers } from 'ethers'
import { ETH, CHAIN_ID_NAME, ERC20_ABI, FACTORY_ADDRESS, FACTORY_ABI } from './constants'
import { ChainIdOrProvider, ChainIdAndProvider, Token, TokenReserves } from './types'
import { normalizeAddress } from './utils'
// abstraction to get contracts
function _getContract(address: string, ABI: string, provider: ethers.providers.BaseProvider): ethers.Contract {
return new ethers.Contract(normalizeAddress(address), ABI, provider)
}
// abstraction to get the chain id + provider
async function _getChainIdAndProvider(chainIdOrProvider: ChainIdOrProvider): Promise<ChainIdAndProvider> {
// if a chainId is provided, get a default provider for it
if (typeof chainIdOrProvider === 'number') {
return {
chainId: chainIdOrProvider,
provider: ethers.getDefaultProvider(CHAIN_ID_NAME[chainIdOrProvider])
}
}
// if a provider is provided, fetch the chainId from it
else {
const { chainId }: { chainId: number } = await chainIdOrProvider.getNetwork()
return {
chainId,
provider: chainIdOrProvider
}
}
}
// abstraction to get token
async function _getToken(tokenAddress: string, chainIdAndProvider: ChainIdAndProvider): Promise<Token> {
if (tokenAddress === ETH) {
return {
chainId: chainIdAndProvider.chainId,
address: ETH,
decimals: 18
}
} else {
const ERC20Contract: ethers.Contract = _getContract(tokenAddress, ERC20_ABI, chainIdAndProvider.provider)
const decimals: number = await ERC20Contract.decimals()
return {
chainId: chainIdAndProvider.chainId,
address: tokenAddress,
decimals
}
}
}
// external function to get token reserves
export async function getTokenReserves(
tokenAddress: string,
chainIdOrProvider: ChainIdOrProvider = 1
): Promise<TokenReserves> {
const chainIdAndProvider: ChainIdAndProvider = await _getChainIdAndProvider(chainIdOrProvider)
// fetch tokens
const ethTokenPromise: Promise<Token> = _getToken(ETH, chainIdAndProvider)
const tokenPromise: Promise<Token> = _getToken(tokenAddress, chainIdAndProvider)
// get contracts
const factoryContract: ethers.Contract = _getContract(
FACTORY_ADDRESS[chainIdAndProvider.chainId],
FACTORY_ABI,
chainIdAndProvider.provider
)
const tokenContract: ethers.Contract = _getContract(tokenAddress, ERC20_ABI, chainIdAndProvider.provider)
const exchangeAddress: string = await factoryContract.getExchange(tokenContract.address)
const exchangeTokenPromise: Promise<Token> = _getToken(exchangeAddress, chainIdAndProvider)
const ethBalancePromise: Promise<ethers.utils.BigNumber> = chainIdAndProvider.provider.getBalance(exchangeAddress)
const tokenBalancePromise: Promise<ethers.utils.BigNumber> = tokenContract.balanceOf(exchangeAddress)
const [ethToken, token, exchangeToken, ethBalance, tokenBalance]: [
Token,
Token,
Token,
ethers.utils.BigNumber,
ethers.utils.BigNumber
] = await Promise.all([ethTokenPromise, tokenPromise, exchangeTokenPromise, ethBalancePromise, tokenBalancePromise])
const ethReserve = { token: ethToken, amount: ethBalance }
const tokenReserve = { token, amount: tokenBalance }
return { token, exchange: exchangeToken, ethReserve, tokenReserve }
}
import BigNumber from 'bignumber.js'
import { MAX_DECIMAL_PLACES, ROUNDING_MODE, FIXED_UNDERFLOW_BEHAVIOR, _0 } from './constants'
import { BigNumberish, FlexibleFormat, FormatSignificantOptions, FormatFixedOptions } from './types'
import { normalizeBigNumberish, ensureBoundedInteger } from './utils'
function _format(bigNumber: BigNumber, format: FlexibleFormat, decimalPlaces: number): string {
return typeof format === 'boolean' && format === false
? bigNumber.toFixed(decimalPlaces)
: bigNumber.toFormat(
decimalPlaces,
ROUNDING_MODE,
typeof format === 'boolean' && format === true ? undefined : format
)
}
// bignumberish is converted to significantDigits, then cast back as a bignumber and formatted, dropping trailing 0s
export function formatSignificant(
bigNumberish: BigNumberish,
{ significantDigits = 6, forceIntegerSignificance = false, format = false }: FormatSignificantOptions
): string {
const bigNumber: BigNumber = normalizeBigNumberish(bigNumberish)
ensureBoundedInteger(significantDigits, [1, MAX_DECIMAL_PLACES])
const minimumSignificantDigits: number = forceIntegerSignificance ? bigNumber.integerValue().toFixed().length : 0
const preciseBigNumber: BigNumber = new BigNumber(
bigNumber.toPrecision(Math.max(minimumSignificantDigits, significantDigits))
)
return _format(preciseBigNumber, format, preciseBigNumber.decimalPlaces())
}
export function formatFixed(
bigNumberish: BigNumberish,
{
decimalPlaces = 4,
dropTrailingZeros = true,
format = false,
underflowBehavior = FIXED_UNDERFLOW_BEHAVIOR.ONE_DIGIT
}: FormatFixedOptions
): string {
const bigNumber: BigNumber = normalizeBigNumberish(bigNumberish)
ensureBoundedInteger(decimalPlaces, MAX_DECIMAL_PLACES)
// this works because we've specified the rounding mode
const minimumNonZeroValue: BigNumber = new BigNumber(decimalPlaces === 0 ? '0.5' : `0.${'0'.repeat(decimalPlaces)}5`)
if (bigNumber.isLessThan(minimumNonZeroValue)) {
switch (underflowBehavior) {
case FIXED_UNDERFLOW_BEHAVIOR.ZERO: {
return _format(_0, format, dropTrailingZeros ? 0 : decimalPlaces)
}
case FIXED_UNDERFLOW_BEHAVIOR.LESS_THAN: {
return `<${_format(minimumNonZeroValue, format, minimumNonZeroValue.decimalPlaces())}`
}
case FIXED_UNDERFLOW_BEHAVIOR.ONE_DIGIT: {
const newBigNumber = new BigNumber(bigNumber.toPrecision(1))
return _format(newBigNumber, format, newBigNumber.decimalPlaces())
}
case FIXED_UNDERFLOW_BEHAVIOR.MAX_DECIMAL_PLACES: {
const newBigNumber = new BigNumber(bigNumber.toFixed(MAX_DECIMAL_PLACES))
return _format(newBigNumber, format, dropTrailingZeros ? newBigNumber.decimalPlaces() : MAX_DECIMAL_PLACES)
}
default: {
throw Error(`Passed FIXED_UNDERFLOW_BEHAVIOR ${underflowBehavior} is not valid.`)
}
}
} else {
const newDecimalPlaces = dropTrailingZeros
? new BigNumber(bigNumber.toFixed(decimalPlaces)).decimalPlaces()
: decimalPlaces
return _format(bigNumber, format, newDecimalPlaces)
}
}
export * from './data'
export * from './computation'
export {
ETH,
SUPPORTED_CHAIN_ID,
FACTORY_ADDRESS,
TRADE_TYPE,
TRADE_EXACT,
FIXED_UNDERFLOW_BEHAVIOR
} from './constants'
import BigNumber from 'bignumber.js'
import { ethers } from 'ethers'
import { SUPPORTED_CHAIN_ID, TRADE_TYPE, TRADE_EXACT, FIXED_UNDERFLOW_BEHAVIOR } from '../constants'
export type BigNumberish = BigNumber | ethers.utils.BigNumber | string | number
export type ChainIdOrProvider = SUPPORTED_CHAIN_ID | ethers.providers.BaseProvider
export interface ChainIdAndProvider {
chainId: number
provider: ethers.providers.BaseProvider
}
export interface Token {
chainId?: SUPPORTED_CHAIN_ID
address?: string
decimals: number
}
export interface TokenAmount {
token: Token
amount: BigNumberish
}
export interface TokenReserves {
token: Token
exchange?: Token
ethReserve: TokenAmount
tokenReserve: TokenAmount
}
export type TokenReservesOptional = TokenReserves | null
export interface TradeDetails {
tradeType: TRADE_TYPE
tradeExact: TRADE_EXACT
inputToken: Token
outputToken: Token
tradeAmount: string
marketRate: string
marketRateInverted: string
}
// formatting options
export type FlexibleFormat = boolean | BigNumber.Format
export interface FormatSignificantOptions {
significantDigits: number
forceIntegerSignificance: boolean
format: FlexibleFormat
}
export interface FormatFixedOptions {
decimalPlaces: number
dropTrailingZeros: boolean
underflowBehavior: FIXED_UNDERFLOW_BEHAVIOR
format: FlexibleFormat
}
import BigNumber from 'bignumber.js'
import { ethers } from 'ethers'
import { _0, MAX_UINT8, MAX_UINT256 } from '../constants'
import { BigNumberish } from '../types'
// check(s) that number(s) is(are) uint8(s)
function ensureUInt8(number: number): void {
if (!Number.isInteger(number) || number < 0 || number > MAX_UINT8) {
throw Error(`Passed number '${number}' is not a valid uint8.`)
}
}
export function ensureAllUInt8(numbers: number[]): void {
numbers.forEach(ensureUInt8)
}
// check(s) that BigNumber(s) is(are) uint256(s)
function ensureUInt256(bigNumber: BigNumber): void {
if (!bigNumber.isInteger() || bigNumber.isLessThan(_0) || bigNumber.isGreaterThan(MAX_UINT256)) {
throw Error(`Passed BigNumber '${bigNumber}' is not a valid uint256.`)
}
}
export function ensureAllUInt256(bigNumbers: BigNumber[]): void {
bigNumbers.forEach(ensureUInt256)
}
// check that number is valid decimals places/significant digits
export function ensureBoundedInteger(number: number, bounds: number | number[]): void {
const [minimum, maximum]: [number, number] = typeof bounds === 'number' ? [0, bounds] : [bounds[0], bounds[1]]
if (!Number.isInteger(number) || number < minimum || number > maximum) {
throw Error(`Passed number '${number}' is not an integer between '${minimum}' and '${maximum}', inclusive.`)
}
}
export function normalizeAddress(address: string): string {
try {
return ethers.utils.getAddress(address.toLowerCase())
} catch {
throw Error(`Passed address '${address}' is not valid.`)
}
}
export function normalizeBigNumberish(bigNumberish: BigNumberish): BigNumber {
try {
const bigNumber = BigNumber.isBigNumber(bigNumberish) ? bigNumberish : new BigNumber(bigNumberish.toString())
if (!bigNumber.isFinite()) {
throw Error(`Passed BigNumberish '${bigNumberish}' of type '${typeof bigNumberish}' is not finite.`)
}
return bigNumber
} catch (error) {
throw Error(`Passed BigNumberish '${bigNumberish}' of type '${typeof bigNumberish}' is invalid. Error: '${error}'.`)
}
}
{
"compilerOptions": {
/* Basic Options */
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": false /* Allow javascript files to be compiled. */,
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
// "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true /* Generates corresponding '.map' file. */,
// "outFile": "./", /* Concatenate and emit output to single file. */
"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. */,
// "composite": true, /* Enable project compilation */
// "incremental": true, /* Enable incremental compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
"removeComments": true /* Do not emit comments to output. */,
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
"strictNullChecks": true /* Enable strict null checks. */,
"strictFunctionTypes": true /* Enable strict checking of function types. */,
"strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */,
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Manually Added Options */
"resolveJsonModule": true
},
"include": ["./src/**/*"],
"exclude": ["./src/__tests__/**/*"]
}
This diff is collapsed.
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