UNPKG

mojito-testnet-sdk

Version:

🛠 An SDK for building applications on top of mojitoswap in testnet.

232 lines (206 loc) • 8.52 kB
import { Price } from './fractions/price' import { TokenAmount } from './fractions/tokenAmount' import invariant from 'tiny-invariant' import JSBI from 'jsbi' import { pack, keccak256 } from '@ethersproject/solidity' import { getCreate2Address } from '@ethersproject/address' import { BigintIsh, FACTORY_ADDRESS, INIT_CODE_HASH, MINIMUM_LIQUIDITY, ZERO, ONE, THIRTY, FIVE, _998, _1000, _10000, ChainId } from '../constants' import { sqrt, parseBigintIsh } from '../utils' import { InsufficientReservesError, InsufficientInputAmountError } from '../errors' import { Token } from './token' let PAIR_ADDRESS_CACHE: { [token0Address: string]: { [token1Address: string]: string } } = {} export class Pair { public readonly liquidityToken: Token private readonly tokenAmounts: [TokenAmount, TokenAmount] public static getAddress(tokenA: Token, tokenB: Token): string { const tokens = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] // does safety checks if (PAIR_ADDRESS_CACHE?.[tokens[0].address]?.[tokens[1].address] === undefined) { PAIR_ADDRESS_CACHE = { ...PAIR_ADDRESS_CACHE, [tokens[0].address]: { ...PAIR_ADDRESS_CACHE?.[tokens[0].address], [tokens[1].address]: getCreate2Address( FACTORY_ADDRESS, keccak256(['bytes'], [pack(['address', 'address'], [tokens[0].address, tokens[1].address])]), INIT_CODE_HASH ) } } } return PAIR_ADDRESS_CACHE[tokens[0].address][tokens[1].address] } public constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount) { const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks ? [tokenAmountA, tokenAmountB] : [tokenAmountB, tokenAmountA] this.liquidityToken = new Token( tokenAmounts[0].token.chainId, Pair.getAddress(tokenAmounts[0].token, tokenAmounts[1].token), 18, 'UNI-V2', 'Uniswap V2' ) this.tokenAmounts = tokenAmounts as [TokenAmount, TokenAmount] } /** * Returns true if the token is either token0 or token1 * @param token to check */ public involvesToken(token: Token): boolean { return token.equals(this.token0) || token.equals(this.token1) } /** * Returns the current mid price of the pair in terms of token0, i.e. the ratio of reserve1 to reserve0 */ public get token0Price(): Price { return new Price(this.token0, this.token1, this.tokenAmounts[0].raw, this.tokenAmounts[1].raw) } /** * Returns the current mid price of the pair in terms of token1, i.e. the ratio of reserve0 to reserve1 */ public get token1Price(): Price { return new Price(this.token1, this.token0, this.tokenAmounts[1].raw, this.tokenAmounts[0].raw) } /** * Return the price of the given token in terms of the other token in the pair. * @param token token to return price of */ public priceOf(token: Token): Price { invariant(this.involvesToken(token), 'TOKEN') return token.equals(this.token0) ? this.token0Price : this.token1Price } /** * Returns the chain ID of the tokens in the pair. */ public get chainId(): ChainId { return this.token0.chainId } public get token0(): Token { return this.tokenAmounts[0].token } public get token1(): Token { return this.tokenAmounts[1].token } public get reserve0(): TokenAmount { return this.tokenAmounts[0] } public get reserve1(): TokenAmount { return this.tokenAmounts[1] } public reserveOf(token: Token): TokenAmount { invariant(this.involvesToken(token), 'TOKEN') return token.equals(this.token0) ? this.reserve0 : this.reserve1 } public getOutputAmount(inputAmount: TokenAmount): [TokenAmount, Pair] { invariant(this.involvesToken(inputAmount.token), 'TOKEN') if (JSBI.equal(this.reserve0.raw, ZERO) || JSBI.equal(this.reserve1.raw, ZERO)) { throw new InsufficientReservesError() } const inputReserve = this.reserveOf(inputAmount.token) const outputReserve = this.reserveOf(inputAmount.token.equals(this.token0) ? this.token1 : this.token0) //remove fee input const inputAmountWithFee = JSBI.multiply(inputAmount.raw, JSBI.subtract(_10000, THIRTY)) const numerator = JSBI.multiply(inputAmountWithFee, outputReserve.raw) const denominator = JSBI.add(JSBI.multiply(inputReserve.raw, _10000), inputAmountWithFee) const outputAmount = new TokenAmount( inputAmount.token.equals(this.token0) ? this.token1 : this.token0, JSBI.divide(numerator, denominator) ) if (JSBI.equal(outputAmount.raw, ZERO)) { throw new InsufficientInputAmountError() } return [outputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))] } public getInputAmount(outputAmount: TokenAmount): [TokenAmount, Pair] { invariant(this.involvesToken(outputAmount.token), 'TOKEN') if ( JSBI.equal(this.reserve0.raw, ZERO) || JSBI.equal(this.reserve1.raw, ZERO) || JSBI.greaterThanOrEqual(outputAmount.raw, this.reserveOf(outputAmount.token).raw) ) { throw new InsufficientReservesError() } const outputReserve = this.reserveOf(outputAmount.token) const inputReserve = this.reserveOf(outputAmount.token.equals(this.token0) ? this.token1 : this.token0) const numerator = JSBI.multiply(JSBI.multiply(inputReserve.raw, outputAmount.raw), _10000) const denominator = JSBI.multiply(JSBI.subtract(outputReserve.raw, outputAmount.raw), JSBI.subtract(_10000, THIRTY)) const inputAmount = new TokenAmount( outputAmount.token.equals(this.token0) ? this.token1 : this.token0, JSBI.add(JSBI.divide(numerator, denominator), ONE) ) return [inputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))] } public getLiquidityMinted( totalSupply: TokenAmount, tokenAmountA: TokenAmount, tokenAmountB: TokenAmount ): TokenAmount { invariant(totalSupply.token.equals(this.liquidityToken), 'LIQUIDITY') const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks ? [tokenAmountA, tokenAmountB] : [tokenAmountB, tokenAmountA] invariant(tokenAmounts[0].token.equals(this.token0) && tokenAmounts[1].token.equals(this.token1), 'TOKEN') let liquidity: JSBI if (JSBI.equal(totalSupply.raw, ZERO)) { liquidity = JSBI.subtract(sqrt(JSBI.multiply(tokenAmounts[0].raw, tokenAmounts[1].raw)), MINIMUM_LIQUIDITY) } else { const amount0 = JSBI.divide(JSBI.multiply(tokenAmounts[0].raw, totalSupply.raw), this.reserve0.raw) const amount1 = JSBI.divide(JSBI.multiply(tokenAmounts[1].raw, totalSupply.raw), this.reserve1.raw) liquidity = JSBI.lessThanOrEqual(amount0, amount1) ? amount0 : amount1 } if (!JSBI.greaterThan(liquidity, ZERO)) { throw new InsufficientInputAmountError() } return new TokenAmount(this.liquidityToken, liquidity) } public getLiquidityValue( token: Token, totalSupply: TokenAmount, liquidity: TokenAmount, feeOn: boolean = false, kLast?: BigintIsh ): TokenAmount { invariant(this.involvesToken(token), 'TOKEN') invariant(totalSupply.token.equals(this.liquidityToken), 'TOTAL_SUPPLY') invariant(liquidity.token.equals(this.liquidityToken), 'LIQUIDITY') invariant(JSBI.lessThanOrEqual(liquidity.raw, totalSupply.raw), 'LIQUIDITY') let totalSupplyAdjusted: TokenAmount if (!feeOn) { totalSupplyAdjusted = totalSupply } else { invariant(!!kLast, 'K_LAST') const kLastParsed = parseBigintIsh(kLast) if (!JSBI.equal(kLastParsed, ZERO)) { const rootK = sqrt(JSBI.multiply(this.reserve0.raw, this.reserve1.raw)) const rootKLast = sqrt(kLastParsed) if (JSBI.greaterThan(rootK, rootKLast)) { const numerator = JSBI.multiply(totalSupply.raw, JSBI.subtract(rootK, rootKLast)) const denominator = JSBI.add(JSBI.multiply(rootK, FIVE), rootKLast) const feeLiquidity = JSBI.divide(numerator, denominator) totalSupplyAdjusted = totalSupply.add(new TokenAmount(this.liquidityToken, feeLiquidity)) } else { totalSupplyAdjusted = totalSupply } } else { totalSupplyAdjusted = totalSupply } } return new TokenAmount( token, JSBI.divide(JSBI.multiply(liquidity.raw, this.reserveOf(token).raw), totalSupplyAdjusted.raw) ) } }