diff --git a/package.json b/package.json index 6e7da37e24..61f8b13a10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hummingbot-gateway", - "version": "1.23.0", + "version": "1.24.0", "description": "Middleware that helps Hummingbot clients access standardized DEX API endpoints on different blockchain networks", "main": "index.js", "license": "Apache-2.0", @@ -32,12 +32,14 @@ "@ethersproject/networks": "5.7.0", "@ethersproject/providers": "5.7.0", "@ethersproject/solidity": "5.7.0", - "@injectivelabs/sdk-ts": "^1.10.58", "@harmony-js/core": "^0.1.57", "@harmony-js/utils": "^0.1.56", "@improbable-eng/grpc-web": "^0.13.0", - "@pancakeswap/sdk": "^2.4.5", + "@injectivelabs/sdk-ts": "^1.10.58", + "@pancakeswap/sdk": "^4.0.0", + "@pancakeswap/smart-router": "^4.2.1", "@pancakeswap/swap-sdk-core": "^1.0.0", + "@pancakeswap/tokens": "^0.1.6", "@pancakeswap/v3-core": "^1.0.2", "@pancakeswap/v3-periphery": "^1.0.2", "@pancakeswap/v3-sdk": "^3.7.0", @@ -77,6 +79,7 @@ "express": "^4.17.1", "express-winston": "^4.1.0", "fs-extra": "^10.0.0", + "graphql-request": "^6.1.0", "http-status-codes": "2.2.0", "immutable": "^4.2.4", "js-yaml": "^4.1.0", @@ -90,15 +93,17 @@ "near-api-js": "1.0.0", "promise-retry": "^2.0.1", "quickswap-sdk": "^3.0.8", + "quipuswap-v3-sdk": "^0.0.7", "swagger-ui-express": "^4.1.6", + "swap-router-sdk": "^1.21.1", "tslib": "^2.3.1", "uuid": "^8.3.2", "vvs-sdk": "^2.4.0", "web3": "^1.7.3", "winston": "^3.3.3", "winston-daily-rotate-file": "^4.5.5", - "xsswap-sdk": "^1.0.1", "xrpl": "^2.7.0", + "xsswap-sdk": "^1.0.1", "yarn": "^1.22.17" }, "devDependencies": { diff --git a/src/amm/amm.controllers.ts b/src/amm/amm.controllers.ts index de2d90b776..a04db850cd 100644 --- a/src/amm/amm.controllers.ts +++ b/src/amm/amm.controllers.ts @@ -58,6 +58,11 @@ import { trade as plentyTrade, estimateGas as plentyEstimateGas, } from '../connectors/plenty/plenty.controllers'; +import { + price as quipuPrice, + trade as quipuTrade, + estimateGas as quipuEstimateGas, +} from '../connectors/quipuswap/quipuswap.controllers'; import { getInitializedChain, getConnector, @@ -75,13 +80,14 @@ import { import { Algorand } from '../chains/algorand/algorand'; import { Tinyman } from '../connectors/tinyman/tinyman'; import { Plenty } from '../connectors/plenty/plenty'; +import { QuipuSwap } from '../connectors/quipuswap/quipuswap'; export async function price(req: PriceRequest): Promise { const chain = await getInitializedChain< Algorand | Ethereumish | Nearish | Tezosish >(req.chain, req.network); - const connector: Uniswapish | RefAMMish | Tinyman | Plenty = - await getConnector( + const connector: Uniswapish | RefAMMish | Tinyman | Plenty | QuipuSwap = + await getConnector( req.chain, req.network, req.connector @@ -89,6 +95,8 @@ export async function price(req: PriceRequest): Promise { if (connector instanceof Plenty) { return plentyPrice(chain, connector, req); + } else if (connector instanceof QuipuSwap) { + return quipuPrice(chain, connector, req); } else if ('routerAbi' in connector) { // we currently use the presence of routerAbi to distinguish Uniswapish from RefAMMish return uniswapPrice(chain, connector, req); @@ -103,8 +111,8 @@ export async function trade(req: TradeRequest): Promise { const chain = await getInitializedChain< Algorand | Ethereumish | Nearish | Tezosish >(req.chain, req.network); - const connector: Uniswapish | RefAMMish | Tinyman | Plenty = - await getConnector( + const connector: Uniswapish | RefAMMish | Tinyman | Plenty | QuipuSwap = + await getConnector( req.chain, req.network, req.connector @@ -112,6 +120,8 @@ export async function trade(req: TradeRequest): Promise { if (connector instanceof Plenty) { return plentyTrade(chain, connector, req); + } else if (connector instanceof QuipuSwap) { + return quipuTrade(chain, connector, req); } else if ('routerAbi' in connector) { // we currently use the presence of routerAbi to distinguish Uniswapish from RefAMMish return uniswapTrade(chain, connector, req); @@ -190,8 +200,8 @@ export async function estimateGas( const chain = await getInitializedChain< Algorand | Ethereumish | Nearish | Tezosish >(req.chain, req.network); - const connector: Uniswapish | RefAMMish | Tinyman | Plenty = - await getConnector( + const connector: Uniswapish | RefAMMish | Tinyman | Plenty | QuipuSwap = + await getConnector( req.chain, req.network, req.connector @@ -199,6 +209,8 @@ export async function estimateGas( if (connector instanceof Plenty) { return plentyEstimateGas(chain, connector); + } else if (connector instanceof QuipuSwap) { + return quipuEstimateGas(chain, connector); } else if ('routerAbi' in connector) { // we currently use the presence of routerAbi to distinguish Uniswapish from RefAMMish return uniswapEstimateGas(chain, connector); diff --git a/src/app.ts b/src/app.ts index cc87758a31..d457994a83 100644 --- a/src/app.ts +++ b/src/app.ts @@ -112,7 +112,7 @@ export const startSwagger = async () => { export const startGateway = async () => { const port = ConfigManagerV2.getInstance().get('server.port'); - const gateway_version="1.23.0"; // gateway version + const gateway_version="1.24.0"; // gateway version if (!ConfigManagerV2.getInstance().get('server.id')) { ConfigManagerV2.getInstance().set( 'server.id', diff --git a/src/chains/tezos/tezos.base.ts b/src/chains/tezos/tezos.base.ts index cff3ff4cc3..d896b217df 100644 --- a/src/chains/tezos/tezos.base.ts +++ b/src/chains/tezos/tezos.base.ts @@ -208,6 +208,9 @@ export class TezosBase { if (spender === 'plenty') { // plenty doesn't need an allowance return { value: constants.MaxUint256, decimals: tokenDecimals }; + } else if (spender === 'quipuswap') { + // quipuswap doesn't need an allowance + return { value: constants.MaxUint256, decimals: tokenDecimals }; } let value = BigNumber.from(0); diff --git a/src/connectors/connectors.routes.ts b/src/connectors/connectors.routes.ts index 9ea7187b97..6c43afc33a 100644 --- a/src/connectors/connectors.routes.ts +++ b/src/connectors/connectors.routes.ts @@ -21,6 +21,7 @@ import { CurveConfig } from './curve/curveswap.config'; import { PlentyConfig } from './plenty/plenty.config'; import { XRPLCLOBConfig } from './xrpl/xrpl.clob.config'; import { KujiraConfig } from './kujira/kujira.config'; +import { QuipuswapConfig } from './quipuswap/quipuswap.config'; export namespace ConnectorsRoutes { export const router = Router(); @@ -162,6 +163,12 @@ export namespace ConnectorsRoutes { 'Enter your kujira account number (input 0 if unsure) >>> ', }, }, + { + name: 'quipuswap', + trading_type: QuipuswapConfig.config.tradingTypes, + chain_type: QuipuswapConfig.config.chainType, + available_networks: QuipuswapConfig.config.availableNetworks, + } ], }); }) diff --git a/src/connectors/pancakeswap/pancakeswap.ts b/src/connectors/pancakeswap/pancakeswap.ts index 0ea4ab6242..cfcb260fda 100644 --- a/src/connectors/pancakeswap/pancakeswap.ts +++ b/src/connectors/pancakeswap/pancakeswap.ts @@ -1,23 +1,24 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { - Fetcher, - Pair, + Currency, + CurrencyAmount, Percent, - Router, - SwapParameters, Token, - TokenAmount, - Trade, + TradeType, } from '@pancakeswap/sdk'; import { BigNumber, - Contract, ContractInterface, ContractTransaction, Transaction, Wallet, } from 'ethers'; import { BinanceSmartChain } from '../../chains/binance-smart-chain/binance-smart-chain'; -import { ExpectedTrade, Uniswapish } from '../../services/common-interfaces'; +import { + ExpectedTrade, + Uniswapish, + UniswapishTrade, +} from '../../services/common-interfaces'; import { percentRegexp } from '../../services/config-manager-v2'; import { InitializationError, @@ -29,7 +30,17 @@ import { logger } from '../../services/logger'; import { isFractionString } from '../../services/validators'; import { PancakeSwapConfig } from './pancakeswap.config'; import routerAbi from './pancakeswap_router_abi.json'; -import { getAddress } from 'ethers/lib/utils'; +import { PublicClient, createPublicClient, http, getAddress } from 'viem'; +import { GraphQLClient } from 'graphql-request'; +import { + Pool, + PoolType, + SmartRouter, + SmartRouterTrade, + SwapRouter, +} from '@pancakeswap/smart-router'; +import { bsc, bscTestnet } from '@wagmi/chains'; +import { MethodParameters } from '@pancakeswap/v3-sdk'; export class PancakeSwap implements Uniswapish { private static _instances: { [name: string]: PancakeSwap }; @@ -41,6 +52,7 @@ export class PancakeSwap implements Uniswapish { private _routerAbi: ContractInterface; private _gasLimitEstimate: number; private _ttl: number; + private _maximumHops: number; private tokenList: Record = {}; private _ready: boolean = false; @@ -52,6 +64,7 @@ export class PancakeSwap implements Uniswapish { this._chain = chain; this._router = config.routerAddress(network); this._ttl = config.ttl; + this._maximumHops = config.maximumHops ?? 1; this._routerAbi = routerAbi.abi; this._gasLimitEstimate = config.gasLimitEstimate; } @@ -130,6 +143,13 @@ export class PancakeSwap implements Uniswapish { return this._ttl; } + /** + * Default maximum number of hops for to go through for a swap transactions. + */ + public get maximumHops(): number { + return this._maximumHops; + } + /** * Gets the allowed slippage percent from the optional parameter or the value * in the configuration. @@ -159,47 +179,38 @@ export class PancakeSwap implements Uniswapish { * @param quoteToken Token input for the transaction * @param baseToken Token output from the transaction * @param amount Amount of `baseToken` desired from the transaction - * @param allowedSlippage (Optional) Fraction in string representing the allowed slippage for this transaction + * @param _allowedSlippage (Optional) Fraction in string representing the allowed slippage for this transaction */ async estimateBuyTrade( quoteToken: Token, baseToken: Token, amount: BigNumber, - allowedSlippage?: string + _allowedSlippage?: string ): Promise { - const nativeTokenAmount: TokenAmount = new TokenAmount( - baseToken, - amount.toString() - ); logger.info( `Fetching pair data for ${quoteToken.address}-${baseToken.address}.` ); - const pair: Pair = await Fetcher.fetchPairData( - quoteToken, - baseToken, - this.bsc.provider - ); - const trades: Trade[] = Trade.bestTradeExactOut( - [pair], - quoteToken, - nativeTokenAmount, - { maxHops: 1 } - ); - if (!trades || trades.length === 0) { + + const trade = await this.getBestTrade(baseToken, quoteToken, amount, TradeType.EXACT_OUTPUT); + + if (!trade) { throw new UniswapishPriceError( - `priceSwapOut: no trade pair found for ${quoteToken.address} to ${baseToken.address}.` + `priceSwapOut: no trade pair found for ${baseToken.address} to ${quoteToken.address}.` ); } logger.info( - `Best trade for ${quoteToken.address}-${baseToken.address}: ` + - `${trades[0].executionPrice.invert().toFixed(6)} ` + - `${baseToken.name}.` + `Best trade for ${baseToken.address}-${quoteToken.address}: ` + + `${trade.inputAmount.toExact()}` + + `${baseToken.symbol}.` ); - const expectedAmount = trades[0].maximumAmountIn( - this.getAllowedSlippage(allowedSlippage) - ); - return { trade: trades[0], expectedAmount }; + return { + trade: { + ...trade, + executionPrice: SmartRouter.getExecutionPrice(trade)!, + }, + expectedAmount: trade.inputAmount, + }; } /** @@ -211,47 +222,38 @@ export class PancakeSwap implements Uniswapish { * @param baseToken Token input for the transaction * @param quoteToken Output from the transaction * @param amount Amount of `baseToken` to put into the transaction - * @param allowedSlippage (Optional) Fraction in string representing the allowed slippage for this transaction + * @param _allowedSlippage (Optional) Fraction in string representing the allowed slippage for this transaction */ async estimateSellTrade( baseToken: Token, quoteToken: Token, amount: BigNumber, - allowedSlippage?: string + _allowedSlippage?: string ): Promise { - const nativeTokenAmount: TokenAmount = new TokenAmount( - baseToken, - amount.toString() - ); logger.info( `Fetching pair data for ${baseToken.address}-${quoteToken.address}.` ); - const pair: Pair = await Fetcher.fetchPairData( - baseToken, - quoteToken, - this.bsc.provider - ); - const trades: Trade[] = Trade.bestTradeExactIn( - [pair], - nativeTokenAmount, - quoteToken, - { maxHops: 1 } - ); - if (!trades || trades.length === 0) { + const trade = await this.getBestTrade(baseToken, quoteToken, amount, TradeType.EXACT_INPUT); + + if (!trade) { throw new UniswapishPriceError( - `priceSwapIn: no trade pair found for ${baseToken} to ${quoteToken}.` + `priceSwapIn: no trade pair found for ${baseToken.address} to ${quoteToken.address}.` ); } logger.info( `Best trade for ${baseToken.address}-${quoteToken.address}: ` + - `${trades[0].executionPrice.toFixed(6)}` + - `${baseToken.name}.` + `${trade.outputAmount.toExact()}` + + `${baseToken.symbol}.` ); - const expectedAmount = trades[0].minimumAmountOut( - this.getAllowedSlippage(allowedSlippage) - ); - return { trade: trades[0], expectedAmount }; + + return { + trade: { + ...trade, + executionPrice: SmartRouter.getExecutionPrice(trade)!, + }, + expectedAmount: trade.outputAmount, + }; } /** @@ -260,9 +262,9 @@ export class PancakeSwap implements Uniswapish { * @param wallet Wallet * @param trade Expected trade * @param gasPrice Base gas price, for pre-EIP1559 transactions - * @param pancakeswapRouter Router smart contract address + * @param pancakeswapRouter Smart Router smart contract address * @param ttl How long the swap is valid before expiry, in seconds - * @param abi Router contract ABI + * @param _abi Router contract ABI * @param gasLimit Gas limit * @param nonce (Optional) EVM transaction nonce * @param maxFeePerGas (Optional) Maximum total fee per gas you want to pay @@ -271,41 +273,48 @@ export class PancakeSwap implements Uniswapish { */ async executeTrade( wallet: Wallet, - trade: Trade, + trade: UniswapishTrade, gasPrice: number, pancakeswapRouter: string, ttl: number, - abi: ContractInterface, + _abi: ContractInterface, gasLimit: number, nonce?: number, maxFeePerGas?: BigNumber, maxPriorityFeePerGas?: BigNumber, allowedSlippage?: string ): Promise { - const result: SwapParameters = Router.swapCallParameters(trade, { - ttl, - recipient: wallet.address, - allowedSlippage: this.getAllowedSlippage(allowedSlippage), - }); + const methodParameters: MethodParameters = SwapRouter.swapCallParameters( + trade as SmartRouterTrade, + { + deadlineOrPreviousBlockhash: Math.floor(Date.now() / 1000 + ttl), + recipient: getAddress(wallet.address), + slippageTolerance: this.getAllowedSlippage(allowedSlippage), + } + ); - const contract: Contract = new Contract(pancakeswapRouter, abi, wallet); if (nonce === undefined) { nonce = await this.bsc.nonceManager.getNextNonce(wallet.address); } + let tx: ContractTransaction; - if (maxFeePerGas || maxPriorityFeePerGas) { - tx = await contract[result.methodName](...result.args, { - gasLimit: gasLimit, - value: result.value, + if (maxFeePerGas !== undefined || maxPriorityFeePerGas !== undefined) { + tx = await wallet.sendTransaction({ + data: methodParameters.calldata, + to: pancakeswapRouter, + gasLimit: gasLimit.toFixed(0), + value: methodParameters.value, nonce: nonce, maxFeePerGas, maxPriorityFeePerGas, }); } else { - tx = await contract[result.methodName](...result.args, { + tx = await wallet.sendTransaction({ + data: methodParameters.calldata, + to: pancakeswapRouter, gasPrice: (gasPrice * 1e9).toFixed(0), gasLimit: gasLimit.toFixed(0), - value: result.value, + value: methodParameters.value, nonce: nonce, }); } @@ -314,4 +323,111 @@ export class PancakeSwap implements Uniswapish { await this.bsc.nonceManager.commitNonce(wallet.address, nonce); return tx; } + + async getPools(currencyA: Currency, currencyB: Currency): Promise { + const v3SubgraphClient = new GraphQLClient( + 'https://api.thegraph.com/subgraphs/name/pancakeswap/exchange-v3-bsc' + ); + const v2SubgraphClient = new GraphQLClient( + 'https://proxy-worker-api.pancakeswap.com/bsc-exchange' + ); + + const pairs = SmartRouter.getPairCombinations(currencyA, currencyB); + + // Create v2 candidate pool fetcher with your own on-chain fetcher + const getV2PoolsByCommonTokenPrices = + SmartRouter.createV2PoolsProviderByCommonTokenPrices( + SmartRouter.getCommonTokenPricesBySubgraph + ); + const getV2CandidatePools = SmartRouter.createGetV2CandidatePools( + getV2PoolsByCommonTokenPrices + ); + + // Define v3 pool on-chain fetcher with customized tvl references + const getV3CandidatePools = SmartRouter.createGetV3CandidatePools( + // Use your customized v3 pool fetcher by default + SmartRouter.getV3PoolsWithTvlFromOnChain, + { + fallbacks: [], + // In millisecond + // Will try fallback fetcher if the default doesn't respond in 2s + fallbackTimeout: 1500, + } + ); + + const allPools = await Promise.allSettled([ + // @ts-ignore + SmartRouter.getStablePoolsOnChain(pairs, () => this.createPublicClient()), + getV2CandidatePools({ + // @ts-ignore + onChainProvider: () => this.createPublicClient(), + // @ts-ignore + v2SubgraphProvider: () => v2SubgraphClient, + // @ts-ignore + v3SubgraphProvider: () => v3SubgraphClient, + currencyA, + currencyB, + }), + getV3CandidatePools({ + // @ts-ignore + onChainProvider: () => this.createPublicClient(), + // @ts-ignore + subgraphProvider: () => v3SubgraphClient, + currencyA, + currencyB, + subgraphCacheFallback: true, + }), + ]); + + const fulfilledPools = allPools.reduce((acc, pool) => { + if (pool.status === 'fulfilled') { + return [...acc, ...pool.value]; + } + return acc; + }, [] as Pool[]); + + return fulfilledPools.flat(); + } + + async getBestTrade(baseToken: Token, quoteToken: Token, amount: BigNumber, tradeType: TradeType): Promise | null> { + const baseTokenAmount: CurrencyAmount = + CurrencyAmount.fromRawAmount(baseToken, amount.toString()); + + const quoteProvider = SmartRouter.createQuoteProvider({ + // @ts-ignore + onChainProvider: () => this.createPublicClient(), + }); + const pools = await this.getPools(baseToken, quoteToken); + + const trade = await SmartRouter.getBestTrade( + baseTokenAmount, + quoteToken, + tradeType, + { + gasPriceWei: () => this.createPublicClient().getGasPrice(), + maxHops: this._maximumHops, + maxSplits: 1, + poolProvider: SmartRouter.createStaticPoolProvider(pools), + quoteProvider, + quoterOptimization: true, + allowedPoolTypes: [PoolType.V2, PoolType.V3, PoolType.STABLE], + } + ); + + return trade; + } + + private createPublicClient(): PublicClient { + const transportUrl = this.bsc.rpcUrl; + + return createPublicClient({ + chain: this.chainId === 56 ? bsc : bscTestnet, + transport: http(transportUrl), + batch: { + multicall: { + batchSize: 1024 * 200, + }, + }, + }); + } } diff --git a/src/connectors/quipuswap/quipuswap.config.ts b/src/connectors/quipuswap/quipuswap.config.ts new file mode 100644 index 0000000000..aa7fb5f3a7 --- /dev/null +++ b/src/connectors/quipuswap/quipuswap.config.ts @@ -0,0 +1,31 @@ +import { ConfigManagerV2 } from '../../services/config-manager-v2'; +import { AvailableNetworks } from '../../services/config-manager-types'; + +export namespace QuipuswapConfig { + export interface NetworkConfig { + allowedSlippage: string; + gasLimitEstimate: number; + apiUrl: (network: string) => string; + tradingTypes: Array; + availableNetworks: Array; + chainType: string; + } + + export const config: NetworkConfig = { + allowedSlippage: ConfigManagerV2.getInstance().get( + 'quipuswap.allowedSlippage' + ), + gasLimitEstimate: ConfigManagerV2.getInstance().get( + 'quipuswap.gasLimitEstimate' + ), + apiUrl: (network: string) => + ConfigManagerV2.getInstance().get( + 'quipuswap.network.' + network + '.apiUrl' + ), + tradingTypes: ['AMM'], + chainType: 'TEZOS', + availableNetworks: [ + { chain: 'tezos', networks: ['mainnet'] }, + ], + }; +} diff --git a/src/connectors/quipuswap/quipuswap.controllers.ts b/src/connectors/quipuswap/quipuswap.controllers.ts new file mode 100644 index 0000000000..ef534acbd2 --- /dev/null +++ b/src/connectors/quipuswap/quipuswap.controllers.ts @@ -0,0 +1,310 @@ +import Decimal from 'decimal.js-light'; +import BigNumber from "bignumber.js"; +import { + HttpException, + LOAD_WALLET_ERROR_CODE, + LOAD_WALLET_ERROR_MESSAGE, + PRICE_FAILED_ERROR_CODE, + PRICE_FAILED_ERROR_MESSAGE, + TRADE_FAILED_ERROR_CODE, + TRADE_FAILED_ERROR_MESSAGE, + SWAP_PRICE_EXCEEDS_LIMIT_PRICE_ERROR_CODE, + SWAP_PRICE_EXCEEDS_LIMIT_PRICE_ERROR_MESSAGE, + SWAP_PRICE_LOWER_THAN_LIMIT_PRICE_ERROR_CODE, + SWAP_PRICE_LOWER_THAN_LIMIT_PRICE_ERROR_MESSAGE, + UNKNOWN_ERROR_ERROR_CODE, + UNKNOWN_ERROR_MESSAGE, + TOKEN_NOT_SUPPORTED_ERROR_MESSAGE, + TOKEN_NOT_SUPPORTED_ERROR_CODE, +} from '../../services/error-handler'; +import { latency } from '../../services/base'; +import { Tezosish } from '../../services/common-interfaces'; +import { logger } from '../../services/logger'; +import { + EstimateGasResponse, + PriceRequest, + PriceResponse, + TradeRequest, + TradeResponse, +} from '../../amm/amm.requests'; +import { TezosToolkit } from '@taquito/taquito'; +import { OperationContentsAndResultTransaction } from '@taquito/rpc'; +import { QuipuSwap } from './quipuswap'; +import { Token, TradeInfo } from './utils/shared/types'; +import { Trade } from 'swap-router-sdk'; + + +async function estimateTradeGasCost( + tezosish: Tezosish, + quipuswap: QuipuSwap, + trade: Trade, + caller?: string +) { + let wallet: TezosToolkit; + try { + wallet = await tezosish.getWallet(caller, undefined, true); + } catch (err) { + logger.error(`Tezos: wallet ${caller} not available.`); + throw new HttpException( + 500, + LOAD_WALLET_ERROR_MESSAGE + err, + LOAD_WALLET_ERROR_CODE + ); + } + + const swapParams = await quipuswap.getSwapParams(wallet, trade); + const batchEstimate = await wallet.estimate.batch(swapParams); + + let gasCost = 0, gasLimitTransaction = 0; + batchEstimate.forEach(estimate => { + gasCost += estimate.totalCost; + gasLimitTransaction += estimate.gasLimit; + }); + const gasPrice = tezosish.gasPrice / 10 ** 6; + return { gasCost, gasLimitTransaction, gasPrice }; +} + +export function getQuipuTrade( + quipuswap: QuipuSwap, + req: PriceRequest +) { + const requestAmount = new BigNumber( + req.amount + ); + + let expectedTrade: TradeInfo; + let expectedAmount: BigNumber; + if (req.side === 'BUY') { + expectedTrade = quipuswap.estimateBuyTrade( + req.base, + req.quote, + requestAmount, + ); + expectedAmount = expectedTrade.inputAmount; + } else { + expectedTrade = quipuswap.estimateSellTrade( + req.base, + req.quote, + requestAmount, + req.allowedSlippage + ); + expectedAmount = expectedTrade.outputAmount; + } + + return { expectedTrade, expectedAmount }; +} + +export async function price( + tezosish: Tezosish, + quipuswap: QuipuSwap, + req: PriceRequest +): Promise { + const startTimestamp: number = Date.now(); + let expectedTrade: TradeInfo; + let expectedAmount: BigNumber; + try { + ({ expectedTrade, expectedAmount } = getQuipuTrade(quipuswap, req)); + } catch (e) { + if (e instanceof Error) { + throw new HttpException( + 500, + PRICE_FAILED_ERROR_MESSAGE + e.message, + PRICE_FAILED_ERROR_CODE + ); + } else { + throw new HttpException( + 500, + UNKNOWN_ERROR_MESSAGE, + UNKNOWN_ERROR_ERROR_CODE + ); + } + } + + const { gasCost, gasLimitTransaction, gasPrice } = await estimateTradeGasCost( + tezosish, + quipuswap, + expectedTrade.trade + ); + + const baseToken: Token = getFullTokenFromSymbol(quipuswap, req.base); + const quoteToken: Token = getFullTokenFromSymbol(quipuswap, req.quote); + + return { + network: tezosish.chain, + timestamp: startTimestamp, + latency: latency(startTimestamp, Date.now()), + base: baseToken.contractAddress, + quote: quoteToken.contractAddress, + amount: new Decimal(req.amount).toFixed(baseToken.metadata.decimals), + rawAmount: new Decimal(req.amount).toFixed(baseToken.metadata.decimals).replace('.', ''), + expectedAmount: new Decimal(expectedAmount.toString()).toFixed(quoteToken.metadata.decimals), + price: new Decimal(expectedTrade.price.toString()).toFixed(8), + gasPrice: gasPrice / 10 ** 6, + gasPriceToken: tezosish.nativeTokenSymbol, + gasLimit: gasLimitTransaction, + gasCost: new Decimal(gasCost).dividedBy(10 ** 6).toFixed(6), + }; +} + +export async function trade( + tezosish: Tezosish, + quipuswap: QuipuSwap, + req: TradeRequest +): Promise { + const startTimestamp: number = Date.now(); + const limitPrice = req.limitPrice; + + let expectedTrade: TradeInfo; + let expectedAmount: BigNumber; + try { + ({ expectedTrade, expectedAmount } = getQuipuTrade(quipuswap, req)); + } catch (e) { + if (e instanceof Error) { + logger.error(`QuipuSwap: could not get trade info - ${e.message}`); + throw new HttpException( + 500, + TRADE_FAILED_ERROR_MESSAGE + e.message, + TRADE_FAILED_ERROR_CODE + ); + } else { + logger.error('QuipuSwap: unknown error trying to get trade info'); + throw new HttpException( + 500, + UNKNOWN_ERROR_MESSAGE, + UNKNOWN_ERROR_ERROR_CODE + ); + } + } + + const { gasCost, gasLimitTransaction, gasPrice } = await estimateTradeGasCost( + tezosish, + quipuswap, + expectedTrade.trade, + req.address + ); + + const baseToken = getFullTokenFromSymbol(quipuswap, req.base); + const quoteToken = getFullTokenFromSymbol(quipuswap, req.quote); + + if (req.side === 'BUY') { + const price = expectedTrade.price; + logger.info( + `Expected execution price is ${price.toString()}, ` + + `limit price is ${limitPrice}.` + ); + if ( + limitPrice && + price.gt(new BigNumber(limitPrice)) + ) { + logger.error('QuipuSwap: swap price exceeded limit price for buy trade'); + throw new HttpException( + 500, + SWAP_PRICE_EXCEEDS_LIMIT_PRICE_ERROR_MESSAGE( + price.toString(), + limitPrice + ), + SWAP_PRICE_EXCEEDS_LIMIT_PRICE_ERROR_CODE + ); + } + + const tx = await quipuswap.executeTrade(tezosish.provider, expectedTrade.trade); + + logger.info( + `Trade has been executed, txHash is ${tx.hash}, gasPrice is ${gasPrice}.` + ); + + return { + network: tezosish.chain, + timestamp: startTimestamp, + latency: latency(startTimestamp, Date.now()), + base: baseToken.contractAddress, + quote: quoteToken.contractAddress, + amount: new Decimal(req.amount).toFixed(baseToken.metadata.decimals), + rawAmount: new Decimal(req.amount).toFixed(baseToken.metadata.decimals).replace('.', ''), + expectedIn: new Decimal(expectedAmount.toString()).toFixed(quoteToken.metadata.decimals), + price: new Decimal(price.toString()).toSignificantDigits(8).toString(), + gasPrice: gasPrice / 10 ** 6, + gasPriceToken: tezosish.nativeTokenSymbol, + gasLimit: gasLimitTransaction, + gasCost: new Decimal(gasCost).dividedBy(10 ** 6).toFixed(6), + txHash: tx.hash, + nonce: parseInt((tx.operations[0] as OperationContentsAndResultTransaction).counter), + }; + } else { + const price = expectedTrade.price; + logger.info( + `Expected execution price is ${price.toString()}, ` + + `limit price is ${limitPrice}.` + ); + if ( + limitPrice && + price.lt(new BigNumber(limitPrice)) + ) { + logger.error('QuipuSwap: swap price lower than limit price for sell trade'); + throw new HttpException( + 500, + SWAP_PRICE_LOWER_THAN_LIMIT_PRICE_ERROR_MESSAGE( + price, + limitPrice + ), + SWAP_PRICE_LOWER_THAN_LIMIT_PRICE_ERROR_CODE + ); + } + + const tx = await quipuswap.executeTrade(tezosish.provider, expectedTrade.trade); + + logger.info( + `Trade has been executed, txHash is ${tx.hash}, gasPrice is ${gasPrice}.` + ); + + return { + network: tezosish.chain, + timestamp: startTimestamp, + latency: latency(startTimestamp, Date.now()), + base: baseToken.contractAddress, + quote: quoteToken.contractAddress, + amount: new Decimal(req.amount).toFixed(baseToken.metadata.decimals), + rawAmount: new Decimal(req.amount).toFixed(baseToken.metadata.decimals).replace('.', ''), + expectedOut: new Decimal(expectedAmount.toString()).toFixed(quoteToken.metadata.decimals), + price: new Decimal(price.toString()).toSignificantDigits(8).toString(), + gasPrice: gasPrice / 10 ** 6, + gasPriceToken: tezosish.nativeTokenSymbol, + gasLimit: gasLimitTransaction, + gasCost: new Decimal(gasCost).dividedBy(10 ** 6).toFixed(6), + txHash: tx.hash, + nonce: parseInt((tx.operations[0] as OperationContentsAndResultTransaction).counter), + }; + } +} + +export function getFullTokenFromSymbol( + quipuswap: QuipuSwap, + tokenSymbol: string +): Token { + try { + return quipuswap.getTokenFromSymbol(tokenSymbol) as Token; + } catch { + throw new HttpException( + 500, + TOKEN_NOT_SUPPORTED_ERROR_MESSAGE + tokenSymbol, + TOKEN_NOT_SUPPORTED_ERROR_CODE + ); + } +} + +export function estimateGas( + tezosish: Tezosish, + quipuswap: QuipuSwap +): EstimateGasResponse { + const gasPrice: number = tezosish.gasPrice / 10 ** 6; + const gasLimitTransaction: number = tezosish.gasLimitTransaction; + const gasLimitEstimate: number = quipuswap.gasLimitEstimate; + return { + network: tezosish.chain, + timestamp: Date.now(), + gasPrice, + gasPriceToken: tezosish.nativeTokenSymbol, + gasLimit: gasLimitTransaction, + gasCost: new BigNumber(Math.ceil(gasPrice * gasLimitEstimate)).dividedBy(10 ** 6).toFixed(6), + }; +} diff --git a/src/connectors/quipuswap/quipuswap.ts b/src/connectors/quipuswap/quipuswap.ts new file mode 100644 index 0000000000..e3d3bd28e1 --- /dev/null +++ b/src/connectors/quipuswap/quipuswap.ts @@ -0,0 +1,121 @@ +import BigNumber from "bignumber.js"; +import { isFractionString } from '../../services/validators'; +import { UniswapishPriceError } from '../../services/error-handler'; +import { QuipuBase } from "./utils/base"; +import { QuipuswapConfig } from "./quipuswap.config"; +import { SupportedNetwork, TradeInfo } from "./utils/shared/types"; +import { Trade } from "swap-router-sdk"; +import { ExecutedTrade } from "../plenty/plenty.types"; +import { TezosToolkit } from "@taquito/taquito"; + + +export class QuipuSwap extends QuipuBase { + private static _instances: { [name: string]: QuipuSwap }; + private _gasLimitEstimate: number; + + constructor(network: SupportedNetwork) { + const config = QuipuswapConfig.config; + super(config.apiUrl(network), network); + this._gasLimitEstimate = config.gasLimitEstimate; + } + + public static getInstance(network: string): QuipuSwap { + if (QuipuSwap._instances === undefined) { + QuipuSwap._instances = {}; + } + if (!(network in QuipuSwap._instances)) { + QuipuSwap._instances[network] = new QuipuSwap(network as SupportedNetwork); + } + + return QuipuSwap._instances[network]; + } + + /** + * Default gas limit used to estimate gasCost for swap transactions. + */ + public get gasLimitEstimate(): number { + return this._gasLimitEstimate; + } + + /** + * Gets the allowed slippage percent from the optional parameter or the value + * in the configuration. + * + * @param allowedSlippageStr (Optional) should be of the form '1/10'. + */ + public getAllowedSlippage(allowedSlippageStr?: string): BigNumber { + if (allowedSlippageStr !== undefined && isFractionString(allowedSlippageStr)) { + const fractionSplit = allowedSlippageStr.split('/'); + const numerator = BigNumber(fractionSplit[0]); + const denominator = BigNumber(fractionSplit[1]); + if (fractionSplit[0] !== '0') + return numerator.multipliedBy(100).dividedBy(denominator); + } + const fractionSplit = QuipuswapConfig.config.allowedSlippage.split('/'); + const numerator = BigNumber(fractionSplit[0]); + const denominator = BigNumber(fractionSplit[1]); + return BigNumber(numerator.multipliedBy(100).dividedBy(denominator)); + } + + /** + * Given the amount of `baseToken` to put into a transaction, calculate the + * amount of `quoteToken` that can be expected from the transaction. + * + * This is typically used for calculating token sell prices. + * + * @param baseToken Token input for the transaction + * @param quoteToken Output from the transaction + * @param amount Amount of `baseToken` to put into the transaction + * @param allowedSlippage (Optional) should be of the form '1/10'. + */ + public estimateSellTrade( + baseToken: string, + quoteToken: string, + amount: BigNumber, + allowedSlippage?: string + ): TradeInfo { + const allowedSlippageBig = this.getAllowedSlippage(allowedSlippage); + return this.getSellingInfo(baseToken, quoteToken, amount, allowedSlippageBig); + } + + /** + * Given the amount of `baseToken` desired to acquire from a transaction, + * calculate the amount of `quoteToken` needed for the transaction. + * + * This is typically used for calculating token buy prices. + * + * @param baseToken Token output from the transaction + * @param quoteToken Token input for the transaction + * @param amount Amount of `baseToken` desired from the transaction + */ + public estimateBuyTrade( + baseToken: string, + quoteToken: string, + amount: BigNumber, + ): TradeInfo { + return this.getBuyingInfo(baseToken, quoteToken, amount); + } + + /** + * Given a wallet and a Uniswap-ish trade, try to execute it on blockchain. + * + * @param wallet TezosToolkit instance + * @param trade Expected trade + */ + async executeTrade( + wallet: TezosToolkit, + trade: Trade, + ): Promise { + const paramsWithKind = await this.getSwapParams(wallet, trade); + const batchOp = await wallet.contract.batch(paramsWithKind).send(); + const status = batchOp.status; + if (status === "applied") { + return { + hash: batchOp.hash, + operations: batchOp.results + }; + } else { + throw new UniswapishPriceError('QuipuSwap: trade failed' + status); + } + } +} diff --git a/src/connectors/quipuswap/utils/api.ts b/src/connectors/quipuswap/utils/api.ts new file mode 100644 index 0000000000..51aa5a6e65 --- /dev/null +++ b/src/connectors/quipuswap/utils/api.ts @@ -0,0 +1,132 @@ +import BigNumber from "bignumber.js"; +import { ResponseInterface } from 'swap-router-sdk/dist/interface/response.interface'; +import { RoutePair } from "swap-router-sdk/dist/interface/route-pair.interface"; +import { Optional, SupportedNetwork, Token } from "./shared/types"; +import { getTokenSlug, getUniqArray, isExist, isMainnet } from "./shared/helpers"; +import { KNOWN_DEX_TYPES, TEZ_TOKEN_MAINNET_WHITELISTED_POOLS_ADDRESSES, TOKEN_TOKEN_MAINNET_WHITELISTED_POOLS, networkTokens } from "./config/config"; +import { extractTokensPools } from "./map.dex.pairs"; +import { ZERO_AMOUNT_BN } from "./config/constants"; +import { DexTypeEnum, RouteDirectionEnum } from "swap-router-sdk"; +import { mapBackendToken } from "./shared/backend.token.map"; +import { QUIPU_TOKEN, TEZOS_TOKEN, WTEZ_TOKEN } from "./config/tokens"; + + +const optionalStringToBigNumber = (value: Optional) => (isExist(value) ? new BigNumber(value) : undefined); + + +const getAllRouterPairs = (routePairsRes: ResponseInterface) => { + const allPairs = routePairsRes.routePairs.map(rawPair => ({ + ...rawPair, + dexId: optionalStringToBigNumber(rawPair.dexId), + dexType: rawPair.dexType, + aTokenPool: new BigNumber(rawPair.aTokenPool), + aTokenMultiplier: optionalStringToBigNumber(rawPair.aTokenMultiplier), + bTokenPool: new BigNumber(rawPair.bTokenPool), + bTokenMultiplier: optionalStringToBigNumber(rawPair.bTokenMultiplier), + cTokenPool: optionalStringToBigNumber(rawPair.cTokenPool), + cTokenMultiplier: optionalStringToBigNumber(rawPair.cTokenMultiplier), + dTokenPool: optionalStringToBigNumber(rawPair.dTokenPool), + dTokenMultiplier: optionalStringToBigNumber(rawPair.dTokenMultiplier), + initialA: optionalStringToBigNumber(rawPair.initialA), + futureA: optionalStringToBigNumber(rawPair.futureA), + fees: rawPair.fees && { + liquidityProvidersFee: optionalStringToBigNumber(rawPair.fees.liquidityProvidersFee), + stakersFee: optionalStringToBigNumber(rawPair.fees.stakersFee), + interfaceFee: optionalStringToBigNumber(rawPair.fees.interfaceFee), + devFee: optionalStringToBigNumber(rawPair.fees.devFee), + swapFee: optionalStringToBigNumber(rawPair.fees.swapFee), + auctionFee: optionalStringToBigNumber(rawPair.fees.auctionFee) + }, + liquidity: optionalStringToBigNumber(rawPair.liquidity), + sqrtPrice: optionalStringToBigNumber(rawPair.sqrtPrice), + curTickIndex: optionalStringToBigNumber(rawPair.curTickIndex), + curTickWitness: optionalStringToBigNumber(rawPair.curTickWitness), + ticks: + rawPair.ticks && + Object.fromEntries( + Object.entries(rawPair.ticks).map(([key, tick]) => [ + key, + { + prev: new BigNumber(tick.prev), + next: new BigNumber(tick.next), + sqrtPrice: new BigNumber(tick.sqrtPrice), + tickCumulativeOutside: new BigNumber(tick.tickCumulativeOutside), + liquidityNet: new BigNumber(tick.liquidityNet) + } + ]) + ), + lastCumulative: rawPair.lastCumulative && { + time: rawPair.lastCumulative.time, + tick: { + sum: new BigNumber(rawPair.lastCumulative.tick.sum), + blockStartValue: new BigNumber(rawPair.lastCumulative.tick.blockStartValue) + } + } + })); + + const filteredPairs = allPairs.filter( + pair => + !pair.aTokenPool.isZero() && + !pair.bTokenPool.isZero() && + (!pair.cTokenPool || !pair.cTokenPool.isZero()) && + (!pair.dTokenPool || !pair.dTokenPool.isZero()) + ); + + return filteredPairs; +}; + + +const tokensMap = (network: SupportedNetwork) => new Map( + networkTokens(network).tokens + .map((token): [string, Token] => { + const mappedToken = mapBackendToken(token); + + return [getTokenSlug(mappedToken), mappedToken]; + }) + .concat([ + [getTokenSlug(TEZOS_TOKEN), TEZOS_TOKEN], + [getTokenSlug(WTEZ_TOKEN(network)), WTEZ_TOKEN(network)], + [getTokenSlug(QUIPU_TOKEN(network)), QUIPU_TOKEN(network)] + ]) +); + + +export const getWhitelistedPairs = (routePairsRes: ResponseInterface, network: SupportedNetwork) => { + const filteredPairs = getAllRouterPairs(routePairsRes); + const routePairs = filteredPairs.filter(routePair => KNOWN_DEX_TYPES.includes(routePair.dexType)); + + const whitelistedPairs = getUniqArray( + routePairs + .filter(pair => { + try { + extractTokensPools( + { + ...pair, + aTokenAmount: ZERO_AMOUNT_BN, + bTokenAmount: ZERO_AMOUNT_BN, + direction: RouteDirectionEnum.Direct + }, + tokensMap(network) + ); + + return ( + (pair.dexType !== DexTypeEnum.QuipuSwap && pair.dexType !== DexTypeEnum.QuipuSwapTokenToTokenDex) || + !isMainnet(network) || + TEZ_TOKEN_MAINNET_WHITELISTED_POOLS_ADDRESSES.includes(pair.dexAddress) || + TOKEN_TOKEN_MAINNET_WHITELISTED_POOLS.some( + ({ address, id }) => pair.dexId?.eq(id) && pair.dexAddress === address + ) + ); + } catch { + return false; + } + }) + .map(({ dexType, dexAddress, dexId }) => ({ dexType, dexAddress, dexId })), + ({ dexAddress, dexId }) => getTokenSlug({ contractAddress: dexAddress, fa2TokenId: dexId?.toNumber() }) + ); + + return { + routePairs, + whitelistedPairs + }; +}; \ No newline at end of file diff --git a/src/connectors/quipuswap/utils/base.ts b/src/connectors/quipuswap/utils/base.ts new file mode 100644 index 0000000000..7c829d0ddd --- /dev/null +++ b/src/connectors/quipuswap/utils/base.ts @@ -0,0 +1,198 @@ +import ws from 'ws'; +import { BigNumber } from 'bignumber.js'; +import { ParamsWithKind, TezosToolkit } from '@taquito/taquito'; +import { getTokens, toAtomic, toReal } from './shared/helpers'; +import { QSNetwork, SupportedNetwork } from './shared/types'; +import { STABLESWAP_REFERRAL, networkInfo } from './config/config'; +import { QUIPUSWAP_REFERRAL_CODE } from './config/constants'; +import { SwapPair } from './shared/types'; +import { getWhitelistedPairs } from './api'; +import { getRoutePairsCombinations } from './swap.router.sdk.adapters'; +import { Trade, getBestTradeExactInput, getBestTradeExactOutput, getTradeInputAmount, getTradeOpParams, getTradeOutputAmount, parseTransferParamsToParamsWithKind } from 'swap-router-sdk'; +import { ResponseInterface } from 'swap-router-sdk/dist/interface/response.interface'; +import { RoutePair } from 'swap-router-sdk/dist/interface/route-pair.interface'; +import { WhitelistedPair } from 'swap-router-sdk/dist/interface/whitelisted-pair.interface'; +import { calculateTradeExactInput, calculateTradeExactOutput } from './trade'; + + +export class QuipuBase { + private readonly _network: QSNetwork; + private _api: ws.WebSocket; + private _routePairs: RoutePair[] = []; + private _whitelistedPairs: WhitelistedPair[] = []; + private _ready: boolean = false; + private initialized: Promise = Promise.resolve(false); + + + constructor(apiUrl: string, network: SupportedNetwork) { + this._network = networkInfo(network); + this._api = new ws(apiUrl); + + this.initialized = new Promise((resolve, reject) => { + this._api.onmessage = (event: ws.MessageEvent) => { + this.parseMessage(event.data.toString()); + this._ready = true; + if (this._routePairs.length > 0) + resolve(true); + }; + this._api.onerror = (error: ws.ErrorEvent) => { + this._ready = false; + reject(error); + }; + this._api.onclose = () => { + this._ready = false; + resolve(false); + } + }); + } + + + public ready = (): boolean => { + return this._ready; + } + + + public init = async () => { + return await this.initialized; + }; + + + public getTokenFromSymbol = (symbol: string) => { + const tokens = getTokens(this._network); + const token = tokens.find(token => token.metadata.symbol === symbol); + if (!token) { + throw new Error(`Token: ${symbol} not found`); + } + return token; + } + + + private parseMessage = (message: string) => { + const rawResponse: ResponseInterface = JSON.parse(message); + const { routePairs, whitelistedPairs } = getWhitelistedPairs(rawResponse, this._network.id); + this._routePairs = routePairs; + this._whitelistedPairs = whitelistedPairs; + }; + + + private getOutputTrade = ( + inputAmount: BigNumber, + swapPair: SwapPair + ) => { + const routePairsCombinations = getRoutePairsCombinations(swapPair, this._routePairs, this._whitelistedPairs); + const atomic = toAtomic(inputAmount, swapPair.inputToken); + const bestTradeExact = getBestTradeExactInput(atomic, routePairsCombinations); + const atomicOutputAmount = getTradeOutputAmount(bestTradeExact); + return { + outputAmount: toReal(atomicOutputAmount ?? BigNumber(0), swapPair.outputToken), + trade: bestTradeExact, + }; + }; + + + private getInputTrade = ( + outputAmount: BigNumber, + swapPair: SwapPair + ) => { + const routePairsCombinations = getRoutePairsCombinations(swapPair, this._routePairs, this._whitelistedPairs); + const atomic = toAtomic(outputAmount, swapPair.outputToken); + const bestTradeExact = getBestTradeExactOutput(atomic, routePairsCombinations); + const atomicInputAmount = getTradeInputAmount(bestTradeExact); + return { + inputAmount: toReal(atomicInputAmount ?? BigNumber(0), swapPair.inputToken), + trade: bestTradeExact + }; + }; + + + protected getSellingInfo = (inputTokenSymbol: string, outputTokenSymbol: string, inputAmount: BigNumber, slippageTolerance: BigNumber) => { + const inputToken = this.getTokenFromSymbol(inputTokenSymbol); + const outputToken = this.getTokenFromSymbol(outputTokenSymbol); + + const swapPair: SwapPair = { inputToken, outputToken }; + const { outputAmount, trade } = this.getOutputTrade(inputAmount, swapPair); + + if (!trade) { + throw new Error('No trade found'); + } + + const bestTradeWithSlippageTolerance = calculateTradeExactInput( + toAtomic(inputAmount, inputToken), + trade, + slippageTolerance.toNumber() + ); + + const lastBestTrade = bestTradeWithSlippageTolerance[bestTradeWithSlippageTolerance.length - 1]; + const outputTokenDecimalPower = BigNumber(10).pow(outputToken.metadata.decimals); + const outputAmountWithSlippage = BigNumber(lastBestTrade.bTokenAmount).div(outputTokenDecimalPower); + + return { + trade: bestTradeWithSlippageTolerance, + inputToken: inputToken, + inputAmount: inputAmount, + outputToken: outputToken, + outputAmount: outputAmountWithSlippage, + price: outputAmount.div(inputAmount), + }; + }; + + + protected getBuyingInfo = (outputTokenSymbol: string, inputTokenSymbol: string, outputAmount: BigNumber) => { + const inputToken = this.getTokenFromSymbol(inputTokenSymbol); + const outputToken = this.getTokenFromSymbol(outputTokenSymbol); + + const swapPair: SwapPair = { inputToken, outputToken }; + const { inputAmount, trade } = this.getInputTrade(outputAmount, swapPair); + + if (!trade) { + throw new Error('No trade found'); + } + + const bestTradeWithSlippageTolerance = calculateTradeExactOutput( + toAtomic(outputAmount, outputToken), + trade + ); + + const firstBestTrade = bestTradeWithSlippageTolerance[0]; + const inputTokenDecimalPower = BigNumber(10).pow(inputToken.metadata.decimals); + const inputAmountWithSlippage = BigNumber(firstBestTrade.aTokenAmount).div(inputTokenDecimalPower); + + return { + trade: bestTradeWithSlippageTolerance, + inputToken: inputToken, + inputAmount: inputAmountWithSlippage, + outputToken: outputToken, + outputAmount: outputAmount, + price: inputAmount.div(outputAmount), + }; + }; + + + public getSwapParams = async ( + tezos: TezosToolkit, + trade: Trade + ) => { + const accountPkh = await tezos.signer.publicKeyHash(); + + type TTK = Parameters[2]; + const tradeTransferParams = await getTradeOpParams( + trade, + accountPkh, + tezos as unknown as TTK, + STABLESWAP_REFERRAL, + accountPkh, + QUIPUSWAP_REFERRAL_CODE.toNumber() + ); + + const walletParamsWithKind = tradeTransferParams.map(tradeTransferParam => + parseTransferParamsToParamsWithKind(tradeTransferParam) as ParamsWithKind + ); + + return walletParamsWithKind; + }; + + + public close = () => { + this._api.close(); + }; +} \ No newline at end of file diff --git a/src/connectors/quipuswap/utils/calculus.ts b/src/connectors/quipuswap/utils/calculus.ts new file mode 100644 index 0000000000..11c7e3451e --- /dev/null +++ b/src/connectors/quipuswap/utils/calculus.ts @@ -0,0 +1,467 @@ + +import { BigNumber } from "bignumber.js"; +import { DexFees } from "swap-router-sdk/dist/interface/dex-fees.interface"; +import { aPrecision, calculateYCache, getDCache, precision } from "./config/constants"; +import assert from "assert"; +import { DexTimestampError, DexWrongIndexError, DexWrongPrecisionError, TooBigPriceChangeErr, assertNonNegative } from "./shared/errors"; +import { dateToSeconds, mockTezosNow } from "./shared/helpers"; +import { TradeOperation } from "swap-router-sdk"; +import { sqrtPriceForTick, calcSwapFee, calcNewPriceX, shiftRight, shiftLeft, calcNewPriceY } from 'quipuswap-v3-sdk/dist/helpers/math' +import { Int, Nat, quipuswapV3Types } from "quipuswap-v3-sdk/dist/types"; + +type P = { + x: BigNumber, + y: BigNumber, + dx: BigNumber, + dy: BigNumber, + u: BigNumber, + n: number +}; + +type SwapRequiredTickState = { + prev: BigNumber; + next: BigNumber; + sqrtPrice: BigNumber; + tickCumulativeOutside: BigNumber; + liquidityNet: BigNumber; +}; + +type SwapRequiredConstants = Pick; + +interface TickCumulative { + sum: BigNumber; + blockStartValue: BigNumber; +} + +interface SwapRequiredCumulative { + time: string; + tick: TickCumulative; +} + +interface SwapRequiredStorage { + liquidity: Nat; + sqrtPrice: Nat; + curTickIndex: Nat; + curTickWitness: Nat; + ticks: Record; + constants: SwapRequiredConstants; + lastCumulative: SwapRequiredCumulative; +} + +interface XToYRecParam { + s: SwapRequiredStorage; + dx: Nat; + dy: Nat; +} + +type YToXRecParam = XToYRecParam; + +const HUNDRED_PERCENT_BPS = 10000; + + +export const util = (x: BigNumber, y: BigNumber) => { + const plus = x.plus(y); + const minus = x.minus(y); + return [plus.exponentiatedBy(8).minus(minus.exponentiatedBy(8)), minus.exponentiatedBy(7).plus(plus.exponentiatedBy(7)).multipliedBy(8)]; +}; + +export const newton = (p: P): BigNumber => { + if (p.n === 0) return p.dy; else { + const _util = util(p.x.plus(p.dx), p.y.minus(p.dy)), + new_u = _util[0], + new_du_dy = _util[1]; // new_u - p.u > 0 because dy remains an underestimate + // dy is an underestimate because we start at 0 and the utility curve is convex + + p.dy = p.dy.plus(new_u.minus(p.u).dividedBy(new_du_dy)); + p.n -= 1; + return newton(p); + } +}; + +type Pool = { + initialA?: BigNumber, + initialATime?: string, + futureA?: BigNumber, + futureATime?: string, + tokensInfo: { + rate?: BigNumber; + reserves: BigNumber; + }[], + fee?: DexFees +}; + +export const ediv = (a: BigNumber, b: BigNumber) => { + let _b$s; + + return a.div(b.abs()).integerValue(BigNumber.ROUND_FLOOR).times((_b$s = b.s) != null ? _b$s : 1); +}; + +const getXp = (_ref: Pool) => { + const tokensInfo = _ref.tokensInfo; + return tokensInfo.map((tokenInfo) => { + return ediv(tokenInfo.rate!.times(tokenInfo.reserves), precision); + }); +}; + +const getA = (t0: Date, a0: BigNumber, t1: Date, a1: BigNumber) => { + const now = mockTezosNow(); + + if (now >= dateToSeconds(t1)) { + return a1; + } + + const tNum = assertNonNegative(now - dateToSeconds(t0), new DexTimestampError("t0=" + t0.toISOString() + " is in the future")); + const tDen = assertNonNegative(dateToSeconds(t1) - dateToSeconds(t0), new DexTimestampError("t1=" + t1.toISOString() + " is before t0=" + t0.toISOString())); + const diff = a1.minus(a0).abs(); + const value = ediv(diff.times(tNum), tDen); + return a1.gt(a0) ? a0.plus(value) : a0.minus(value).abs(); // always a0 > (a0-a1) * (now-t0)/(t1-t0) if t1 > now && a0 > a1 +}; + +const getD = (xp: BigNumber[], ampF: BigNumber): BigNumber => { + const cacheKey = xp.map((x) => { + return x.toFixed(); + }).join(',') + "," + ampF.toFixed(); + + if (getDCache.has(cacheKey)) { + return getDCache.get(cacheKey); + } + + const sumC = xp.reduce((acc, value) => { + return acc.plus(value); + }, new BigNumber(0)); + const tokensCount = xp.length; + const aNnF = ampF.times(tokensCount); + let d = sumC; + let prevD = new BigNumber(0); + + const _loop = () => { + const dConst = d; + const counted = xp.reduce((accum, value) => { + return [accum[0].times(dConst), accum[1].times(value.times(tokensCount))]; + }, [d, new BigNumber(1)]); + const dP = ediv(counted[0], counted[1]); + prevD = d; + d = ediv(ediv(aNnF.times(sumC), aPrecision).plus(dP.times(tokensCount)).times(d), ediv(assertNonNegative(aNnF.minus(aPrecision), new DexWrongPrecisionError('One of tokens has a wrong precision')).times(d), aPrecision).plus(new BigNumber(tokensCount).plus(1).times(dP))); // Equality with the precision of 1 + }; + + while (d.minus(prevD).abs().gt(1)) { + _loop(); + } + + getDCache.set(cacheKey, d); + return d; +}; + +const calculateY = (c: BigNumber, aNnF: BigNumber, s_: BigNumber, d: BigNumber, tokensCount: number) => { + const cacheKey = "" + [c, aNnF, s_, d, tokensCount].map((x) => { + return x.toFixed(); + }).join(','); + + if (calculateYCache.has(cacheKey)) { + return calculateYCache.get(cacheKey); + } + + c = c.times(d).times(aPrecision).div(aNnF.times(tokensCount)).integerValue(BigNumber.ROUND_CEIL); + const b = s_.plus(ediv(d.times(aPrecision), aNnF)); + let y = d; + let prevY = new BigNumber(0); + + while (y.minus(prevY).abs().gt(1)) { + prevY = y; + y = y.pow(2).plus(c).div(assertNonNegative(y.times(2).plus(b).minus(d))).integerValue(BigNumber.ROUND_CEIL); + } + + calculateYCache.set(cacheKey, y); + return y; +}; + +const getY = (i: number, j: number, x: BigNumber, xp: BigNumber[], s: Pool) => { + const tokensCount = s.tokensInfo.length; + assert(i !== j, 'Both tokens are same'); + const ampF = getA(new Date(s.initialATime!), s.initialA!, new Date(s.futureATime!), s.futureA!); + const aNnF = ampF.times(tokensCount); + const d = getD(xp, ampF); + + const prepareParams = (accum: { s_: BigNumber, c: BigNumber[] }, value: BigNumber, iter: number) => { + if (iter !== j) { + const _x = iter === i ? x : value; + + accum.s_ = accum.s_.plus(_x); + accum.c[0] = accum.c[0].times(d); + accum.c[1] = accum.c[1].times(_x.times(tokensCount)); + } + + return accum; + }; + + const res = xp.reduce(prepareParams, { + s_: new BigNumber(0), + c: [d, new BigNumber(1)] + }); + const c = res.c[0].div(res.c[1]).integerValue(BigNumber.ROUND_CEIL); + return calculateY(c, aNnF, res.s_, d, s.tokensInfo.length); +}; + +export const performSwap = (i: number, j: number, dx: BigNumber, pool: Pool) => { + const xp = getXp(pool); + const xpI = xp[i]; + const xpJ = xp[j]; + const tI = pool.tokensInfo[i]; + const tJ = pool.tokensInfo[j]; + assert(xpI && tI, new DexWrongIndexError(i)); + assert(xpJ && tJ, new DexWrongIndexError(j)); + const rateIF = tI.rate!; + const rateJF = tJ.rate!; + const x = xpI.plus(ediv(dx.times(rateIF), precision)); + const y = getY(i, j, x, xp, pool); + const dy = assertNonNegative(xpJ.minus(y)); + return ediv(dy.times(precision), rateJF); +}; + +export const sumAllFees = (fees: DexFees) => { + return Object.values(fees).reduce((sum, value) => { + return sum.plus(value != null ? value : 0); + }, new Nat(0)); +}; + +export const makeSwapRequiredStorage = (pair: TradeOperation) => { + const liquidity = new Nat(pair.liquidity!.toString()), + sqrtPrice = new Nat(pair.sqrtPrice!.toString()), + curTickIndex = new Nat(pair.curTickIndex!.toString()), + curTickWitness = new Nat(pair.curTickWitness!.toString()), + ticks = pair.ticks, + lastCumulative = pair.lastCumulative as SwapRequiredCumulative, + fees = pair.fees; + return { + liquidity: liquidity, + sqrtPrice: sqrtPrice, + curTickIndex: curTickIndex, + curTickWitness: curTickWitness, + lastCumulative: lastCumulative, + ticks: ticks!, + constants: { + feeBps: fees == null ? new Nat(0) : new Nat(fees.liquidityProvidersFee!.toString()) + }, + }; +}; + +const floorLogHalfBps = (x: Nat, y: Nat, outOfBoundsError: Error) => { + const tenx = x.multipliedBy(10); + + if (tenx.isLessThan(y.multipliedBy(7)) || tenx.isGreaterThan(y.multipliedBy(15))) { + throw outOfBoundsError; + } + + const xPlusY = x.plus(y); + const num = x.toBignumber().minus(y).multipliedBy(60003).multipliedBy(xPlusY); + const denom = xPlusY.multipliedBy(xPlusY).plus(x.multipliedBy(2).multipliedBy(y)); + return num.dividedToIntegerBy(denom); +} + +const fixCurTickIndexRec = ( + curTickIndexNew: Int, + curIndexSqrtPrice: Nat, + sqrtPriceNew: Nat +): Int => { + if (sqrtPriceNew.isLessThan(curIndexSqrtPrice)) { + const prevTickIndex = curTickIndexNew.minus(1); + const prevIndexSqrtPrice = sqrtPriceForTick(prevTickIndex); + + return fixCurTickIndexRec(prevTickIndex, prevIndexSqrtPrice, sqrtPriceNew); + } else { + const nextTickIndex = curTickIndexNew.plus(1); + const nextIndexSqrtPrice = sqrtPriceForTick(nextTickIndex); + + if (nextIndexSqrtPrice.isLessThanOrEqualTo(sqrtPriceNew)) { + return fixCurTickIndexRec(nextTickIndex, nextIndexSqrtPrice, sqrtPriceNew); + } else { + return curTickIndexNew; + } + } +} + +const fixCurTickIndex = (curTickIndex: Int, sqrtPriceNew: Nat) => { + return fixCurTickIndexRec(curTickIndex, sqrtPriceForTick(curTickIndex), sqrtPriceNew); +} + +const calcNewCurTickIndex = (curTickIndex: Int, sqrtPriceOld: Nat, sqrtPriceNew: Nat) => { + const curTickIndexDelta = floorLogHalfBps( + sqrtPriceNew, + sqrtPriceOld, + new TooBigPriceChangeErr() + ); + + const curTickIndexNew = curTickIndex.plus(curTickIndexDelta); + + return fixCurTickIndex(curTickIndexNew, sqrtPriceNew); +} + +const oneMinusFeeBps = (feeBps: Nat) => { + return new Nat(HUNDRED_PERCENT_BPS).minus(feeBps); +} + +const xToYRec = (p: XToYRecParam): XToYRecParam => { + if (p.s.liquidity.isZero()) { + return p; + } + + let totalFee = calcSwapFee(p.s.constants.feeBps, p.dx.toBignumber()); + let sqrtPriceNew = calcNewPriceX(p.s.sqrtPrice as quipuswapV3Types.x80n, p.s.liquidity, p.dx.minus(totalFee)); + const curTickIndexNew = calcNewCurTickIndex(p.s.curTickIndex as Int, p.s.sqrtPrice, sqrtPriceNew); + if (curTickIndexNew.gte(p.s.curTickWitness)) { + const dy = shiftRight( + p.s.sqrtPrice.toBignumber().minus(sqrtPriceNew).multipliedBy(p.s.liquidity), + new BigNumber(80) + ).integerValue(BigNumber.ROUND_FLOOR); + const newStorage = { + ...p.s, + sqrtPrice: sqrtPriceNew, + curTickIndex: curTickIndexNew + }; + + return { + s: newStorage, + dx: new Nat(0), + dy: p.dy.plus(dy) + }; + } + const tick = p.s.ticks[p.s.curTickWitness.toFixed()]; + const loNew = tick.prev; + sqrtPriceNew = new quipuswapV3Types.x80n(tick.sqrtPrice.minus(1)); + const dy = shiftRight( + p.s.sqrtPrice.minus(sqrtPriceNew).multipliedBy(p.s.liquidity), + new BigNumber(80) + ).integerValue(BigNumber.ROUND_FLOOR); + const dxForDy = shiftLeft(dy, new BigNumber(160)) + .dividedBy(p.s.sqrtPrice.multipliedBy(sqrtPriceNew)) + .integerValue(BigNumber.ROUND_CEIL); + const dxConsumed = dxForDy + .multipliedBy(HUNDRED_PERCENT_BPS) + .dividedBy(oneMinusFeeBps(p.s.constants.feeBps)) + .integerValue(BigNumber.ROUND_CEIL); + totalFee = dxConsumed.minus(dxForDy); + const sums = p.s.lastCumulative; + const tickCumulativeOutsideNew = sums.tick.sum.minus(tick.tickCumulativeOutside); + const tickNew = { + ...tick, + tickCumulativeOutside: tickCumulativeOutsideNew + }; + const ticksNew: Record = { + ...p.s.ticks, + [p.s.curTickWitness.toFixed()]: tickNew + }; + const storageNew = { + ...p.s, + curTickWitness: new Nat(loNew.toString()), + sqrtPrice: sqrtPriceNew, + curTickIndex: curTickIndexNew.minus(1), + ticks: ticksNew, + liquidity: p.s.liquidity.minus(tick.liquidityNet) + }; + const paramNew = { + s: storageNew, + dx: p.dx.minus(dxConsumed), + dy: p.dy.plus(dy) + }; + + return xToYRec(paramNew); +} + +export const calculateXToY = (s: SwapRequiredStorage, dx: Nat) => { + const r = xToYRec({ s, dx, dy: new Nat(0) }); + + return { + output: r.dy, + inputLeft: r.dx, + newStoragePart: r.s + }; +} + +export const yToXRec = (p: YToXRecParam): YToXRecParam => { + if (p.s.liquidity.isZero()) { + return p; + } + + let totalFee = calcSwapFee(p.s.constants.feeBps.toBignumber(), p.dy.toBignumber()); + let dyMinusFee = p.dy.minus(totalFee); + let sqrtPriceNew = calcNewPriceY(p.s.sqrtPrice, p.s.liquidity, dyMinusFee); + const curTickIndexNew = calcNewCurTickIndex(p.s.curTickIndex, p.s.sqrtPrice, sqrtPriceNew); + const tick = p.s.ticks[p.s.curTickWitness.toFixed()]; + const nextTickIndex = tick.next; + if (curTickIndexNew.lt(nextTickIndex)) { + const dx = p.s.liquidity + .toBignumber() + .multipliedBy(shiftLeft(sqrtPriceNew.toBignumber().minus(p.s.sqrtPrice), new BigNumber(80))) + .dividedBy(sqrtPriceNew.multipliedBy(p.s.sqrtPrice)) + .integerValue(BigNumber.ROUND_FLOOR); + const sNew = { + ...p.s, + sqrtPrice: new quipuswapV3Types.x80n(sqrtPriceNew), + curTickIndex: curTickIndexNew + }; + + return { s: sNew, dy: new Nat(0), dx: p.dx.plus(dx) }; + } + + const nextTick = p.s.ticks[nextTickIndex.toFixed()]; + sqrtPriceNew = new Nat(nextTick.sqrtPrice.toString()); + + const dx = new Nat( + p.s.liquidity + .toBignumber() + .multipliedBy(shiftLeft(sqrtPriceNew.toBignumber().minus(p.s.sqrtPrice), new BigNumber(80))) + .dividedBy(sqrtPriceNew.multipliedBy(p.s.sqrtPrice)) + .integerValue(BigNumber.ROUND_FLOOR) + ); + const _280 = new BigNumber(2).pow(80); + const dyForDx = new Nat( + p.s.liquidity + .toBignumber() + .multipliedBy(sqrtPriceNew.toBignumber().minus(p.s.sqrtPrice)) + .dividedBy(_280) + .integerValue(BigNumber.ROUND_CEIL) + ); + dyMinusFee = dyForDx; + const dyConsumed = dyMinusFee + .toBignumber() + .multipliedBy(HUNDRED_PERCENT_BPS) + .dividedBy(oneMinusFeeBps(p.s.constants.feeBps)) + .integerValue(BigNumber.ROUND_CEIL); + totalFee = dyConsumed.minus(dyForDx); + const sums = p.s.lastCumulative; + const tickCumulativeOutsideNew = sums.tick.sum.minus(nextTick.tickCumulativeOutside); + const nextTickNew = { + ...nextTick, + tickCumulativeOutside: tickCumulativeOutsideNew + }; + const ticksNew: Record = { + ...p.s.ticks, + [nextTickIndex.toFixed()]: nextTickNew + }; + const storageNew = { + ...p.s, + sqrtPrice: new quipuswapV3Types.x80n(sqrtPriceNew), + curTickWitness: new Nat(nextTickIndex.toString()), + curTickIndex: new Nat(nextTickIndex.toString()), + ticks: ticksNew, + liquidity: new Nat(p.s.liquidity.plus(nextTick.liquidityNet)) + }; + const paramNew = { + s: storageNew, + dy: p.dy.minus(dyConsumed), + dx: p.dx.plus(dx) + }; + + return yToXRec(paramNew); +} + + +export const calculateYToX = (s: SwapRequiredStorage, dy: Nat) => { + const r = yToXRec({ s, dy, dx: new Nat(0) }); + + return { + output: r.dx, + inputLeft: r.dy, + newStoragePart: r.s + }; +} diff --git a/src/connectors/quipuswap/utils/config/config.ts b/src/connectors/quipuswap/utils/config/config.ts new file mode 100644 index 0000000000..2db9d86d15 --- /dev/null +++ b/src/connectors/quipuswap/utils/config/config.ts @@ -0,0 +1,78 @@ +import GhostnetTokens from '../../../../templates/lists/tezos.ghostnet.tokens.json'; +import MainnetTokens from '../../../../templates/lists/tezos.mainnet.tokens.json'; +import { DexTypeEnum } from 'swap-router-sdk'; +import { ConnectType, QSNetwork, QSNetworkType, NetworkType, SupportedNetwork, Token } from '../shared/types'; +import { mapBackendToken } from '../shared/backend.token.map'; + +export const KNOWN_DEX_TYPES = [ + DexTypeEnum.QuipuSwap, + DexTypeEnum.QuipuSwapTokenToTokenDex, + DexTypeEnum.QuipuSwapCurveLike, + DexTypeEnum.QuipuSwap20, + DexTypeEnum.QuipuSwapV3, + DexTypeEnum.YupanaWtez +]; + +const tokenStandardiser = (token: typeof MainnetTokens.tokens[0]): Token => { + // token.symbol = token.symbol.toUpperCase(); + // return token; + return mapBackendToken({ + contractAddress: token.address ?? 'tez', + type: token.standard, + fa2TokenId: token.tokenId ?? undefined, + metadata: { + decimals: token.decimals, + name: token.name, + symbol: token.symbol.toUpperCase(), + } + }) +}; + +const TOKENS_MAP = { + [NetworkType.MAINNET]: { + ...MainnetTokens, + tokens: MainnetTokens.tokens.map(tokenStandardiser) + }, + [NetworkType.GHOSTNET]: { + ...GhostnetTokens, + tokens: GhostnetTokens.tokens.map(tokenStandardiser) + } +}; + +export const networkTokens = (network: SupportedNetwork) => TOKENS_MAP[network]; + +export const TEZ_TOKEN_MAINNET_WHITELISTED_POOLS_ADDRESSES = [ + 'KT1K4EwTpbvYN9agJdjpyJm4ZZdhpUNKB3F6', + 'KT1WxgZ1ZSfMgmsSDDcUn8Xn577HwnQ7e1Lb', + 'KT1PL1YciLdwMbydt21Ax85iZXXyGSrKT2BE', + 'KT1KFszq8UFCcWxnXuhZPUyHT9FK3gjmSKm6', + 'KT1Ucg1fTZXBD8P426rTRXyu7YQUgYXV7RVu', + 'KT1EtjRRCBC2exyCRXz8UfV7jz7svnkqi7di', + 'KT1X3zxdTzPB9DgVzA3ad6dgZe9JEamoaeRy' +]; + +export const TOKEN_TOKEN_MAINNET_WHITELISTED_POOLS = [{ address: 'KT1VNEzpf631BLsdPJjt2ZhgUitR392x6cSi', id: 21 }]; + +const MAINNET_NETWORK: QSNetwork = { + id: NetworkType.MAINNET as SupportedNetwork, + connectType: ConnectType.DEFAULT, + name: 'Mainnet', + type: QSNetworkType.MAIN, + disabled: false +}; + +const GHOSTNET_NETWORK: QSNetwork = { + ...MAINNET_NETWORK, + id: NetworkType.GHOSTNET as SupportedNetwork, + name: 'Ghostnet', + type: QSNetworkType.TEST +}; + +const networks = { + [NetworkType.MAINNET]: MAINNET_NETWORK, + [NetworkType.GHOSTNET]: GHOSTNET_NETWORK, +}; + +export const networkInfo = (network: SupportedNetwork) => networks[network]; + +export const STABLESWAP_REFERRAL = 'tz1Sw2mFAUzbkm7dkGCDrbeBsJTTtV7JD8Ey'; diff --git a/src/connectors/quipuswap/utils/config/constants.ts b/src/connectors/quipuswap/utils/config/constants.ts new file mode 100644 index 0000000000..13f20dfb2b --- /dev/null +++ b/src/connectors/quipuswap/utils/config/constants.ts @@ -0,0 +1,22 @@ +import BigNumber from 'bignumber.js'; + +export const ZERO_AMOUNT = 0; +export const ZERO_AMOUNT_BN = new BigNumber(ZERO_AMOUNT); +export const MAX_HOPS_COUNT = 3; + +// Referal code +export const QUIPUSWAP_REFERRAL_CODE = new BigNumber(1); + +export const precision = new BigNumber(1e18); + +export const aPrecision = new BigNumber(100); + +export const getDCache = new Map(); + +export const calculateYCache = new Map(); + +export const feeDenominator = new BigNumber(10000000000); + +export const SWAP_RATIO_DENOMINATOR = new BigNumber('1000000000000000000'); + +export const SAVED_TOKENS_KEY = 'savedCustomTokens'; \ No newline at end of file diff --git a/src/connectors/quipuswap/utils/config/tokens.ts b/src/connectors/quipuswap/utils/config/tokens.ts new file mode 100644 index 0000000000..236d43bd2a --- /dev/null +++ b/src/connectors/quipuswap/utils/config/tokens.ts @@ -0,0 +1,57 @@ +import { NetworkType, Standard, SupportedNetwork, Token } from "../shared/types"; + +export const TEZOS_TOKEN: Token = { + type: Standard.Fa12, + contractAddress: 'tez', + metadata: { + decimals: 6, + name: 'Tezos', + symbol: 'XTZ', + } +}; + +export const MAINNET_QUIPU_TOKEN: Token = { + type: Standard.Fa2, + contractAddress: 'KT193D4vozYnhGJQVtw7CoxxqphqUEEwK6Vb', + fa2TokenId: 0, + metadata: { + decimals: 6, + symbol: 'QUIPU', + name: 'Quipuswap Governance Token', + } +}; + +const GHOSTNET_QUIPU_TOKEN: Token = { + ...MAINNET_QUIPU_TOKEN, + contractAddress: 'KT19363aZDTjeRyoDkSLZhCk62pS4xfvxo6c' +}; + +export const networksQuipuTokens = { + [NetworkType.MAINNET]: MAINNET_QUIPU_TOKEN, + [NetworkType.GHOSTNET]: GHOSTNET_QUIPU_TOKEN +}; + +export const MAINNET_WTEZ_TOKEN: Token = { + type: Standard.Fa2, + contractAddress: 'KT1UpeXdK6AJbX58GJ92pLZVCucn2DR8Nu4b', + fa2TokenId: 0, + metadata: { + decimals: 6, + symbol: 'wTEZ', + name: 'Wrapped Tezos FA2 token', + } +}; + +export const GHOSTNET_WTEZ_TOKEN: Token = { + ...MAINNET_WTEZ_TOKEN, + contractAddress: 'KT1L8ujeb25JWKa4yPB61ub4QG2NbaKfdJDK' +}; + +export const networksWtezTokens = { + [NetworkType.MAINNET]: MAINNET_WTEZ_TOKEN, + [NetworkType.GHOSTNET]: GHOSTNET_WTEZ_TOKEN +}; + +export const QUIPU_TOKEN = (network: SupportedNetwork) => networksQuipuTokens[network]; + +export const WTEZ_TOKEN = (network: SupportedNetwork) => networksWtezTokens[network]; diff --git a/src/connectors/quipuswap/utils/map.dex.pairs.ts b/src/connectors/quipuswap/utils/map.dex.pairs.ts new file mode 100644 index 0000000000..824f58f96e --- /dev/null +++ b/src/connectors/quipuswap/utils/map.dex.pairs.ts @@ -0,0 +1,48 @@ +import { TradeOperation } from "swap-router-sdk"; +import { TokensMap } from "./shared/types"; +import { swapRouterSdkTokenSlugToQuipuTokenSlug } from "./swap.router.sdk.adapters"; +import { isExist } from "./shared/helpers"; + + +const SECOND_TUPLE_INDEX = 1; +const SINGLE_TOKEN_VALUE = 1; + + +export const extractTokensPools = (operation: TradeOperation, knownTokens: TokensMap) => { + const { + aTokenSlug, + bTokenSlug, + cTokenSlug, + dTokenSlug, + aTokenPool, + bTokenPool, + cTokenPool, + dTokenPool, + aTokenStandard, + bTokenStandard, + cTokenStandard, + dTokenStandard + } = operation; + + const tokensAmounts = [aTokenPool, bTokenPool, cTokenPool, dTokenPool]; + const tokensStandards = [aTokenStandard, bTokenStandard, cTokenStandard, dTokenStandard]; + const tokensPools = [aTokenSlug, bTokenSlug, cTokenSlug, dTokenSlug] + .map((tokenSlug, index) => { + if (!tokenSlug) { + return null; + } + + const token = knownTokens.get(swapRouterSdkTokenSlugToQuipuTokenSlug(tokenSlug, tokensStandards[index])); + + if (!isExist(token)) { + throw new Error(`No Token Metadata of ${tokenSlug}`); + } + + return { token, pool: tokensAmounts[index]! }; + }) + .filter(isExist); + const [tokenBPool] = tokensPools.splice(SECOND_TUPLE_INDEX, SINGLE_TOKEN_VALUE); + tokensPools.push(tokenBPool); + + return tokensPools; +}; diff --git a/src/connectors/quipuswap/utils/shared/backend.token.map.ts b/src/connectors/quipuswap/utils/shared/backend.token.map.ts new file mode 100644 index 0000000000..8739f0c481 --- /dev/null +++ b/src/connectors/quipuswap/utils/shared/backend.token.map.ts @@ -0,0 +1,12 @@ +import { RawToken, Standard, Token } from './types'; + +export const mapBackendToken = (raw: RawToken, newSymbol?: string): Token => ({ + ...raw, + fa2TokenId: raw.fa2TokenId === undefined ? undefined : Number(raw.fa2TokenId), + type: raw.type as Standard, + metadata: { + ...raw.metadata, + decimals: raw.metadata.decimals, + symbol: newSymbol ?? raw.metadata.symbol + } +}); \ No newline at end of file diff --git a/src/connectors/quipuswap/utils/shared/errors.ts b/src/connectors/quipuswap/utils/shared/errors.ts new file mode 100644 index 0000000000..9b68eeced6 --- /dev/null +++ b/src/connectors/quipuswap/utils/shared/errors.ts @@ -0,0 +1,42 @@ +import BigNumber from "bignumber.js"; + +export class InvalidTokensListError extends Error { + constructor(json: unknown) { + super(`Invalid response for tokens list was received: ${JSON.stringify(json)}`); + } +} + +export class DexWrongIndexError extends Error { + constructor(index: number) { + super("There is no asset with index " + index + " in the pool"); + } +} + +export class MathNatError extends Error { + constructor(value: BigNumber) { + super("A non-negative value was expected but actual one is " + value.toFixed()); + } + +} + +export class DexTimestampError extends Error { } + +export class DexWrongPrecisionError extends Error { }; + +export class TooBigPriceChangeErr extends Error { }; + +export class DexFeeOverflowError extends Error { + constructor(output: BigNumber, fee: BigNumber) { + super("The possible output (" + output.toFixed() + ") is less than fee (" + fee.toFixed() + ")"); + } +} + +export const assertNonNegative = (rawValue: number | BigNumber, error?: Error) => { + const value = new BigNumber(rawValue); + + if (value.gte(0)) { + return value; + } + + throw error != null ? error : new MathNatError(value); +}; \ No newline at end of file diff --git a/src/connectors/quipuswap/utils/shared/helpers.ts b/src/connectors/quipuswap/utils/shared/helpers.ts new file mode 100644 index 0000000000..444384e494 --- /dev/null +++ b/src/connectors/quipuswap/utils/shared/helpers.ts @@ -0,0 +1,94 @@ +import BigNumber from "bignumber.js"; +import { NetworkType, Nullable, Optional, QSNetwork, Standard, SupportedNetwork, Token, TokenAddress, TokenId, TokenWithQSNetworkType, Undefined } from "./types"; +import { TEZOS_TOKEN, networksQuipuTokens } from "../config/tokens"; +import { mapBackendToken } from "./backend.token.map"; +import { InvalidTokensListError } from "./errors"; +import { networkTokens } from "../config/config"; + +export const isMainnet = (network: SupportedNetwork) => network === NetworkType.MAINNET; + +export const isNull = (value: Nullable): value is null => value === null; +export const isUndefined = (value: Undefined): value is undefined => value === undefined; +export const isExist = (value: Optional): value is T => !isNull(value) && !isUndefined(value); + +const SEPARATOR = '_'; +const FALLBACK_DECIMALS = 0; + +export const getUniqArray = (list: T[], getKey: (el: T) => string): T[] => { + const map: Record = {}; + + return list.filter(el => { + const key = getKey(el); + if (!(key in map)) { + map[key] = true; + + return true; + } + + return false; + }); +}; + +export const getTokenIdFromSlug = (slug: string): TokenId => { + const [contractAddress, fa2TokenId] = slug.split('_'); + + return { + contractAddress, + fa2TokenId: fa2TokenId ? +fa2TokenId : undefined, + type: fa2TokenId === undefined ? Standard.Fa12 : Standard.Fa2 + }; +}; + +export const getTokenSlug = (token: TokenAddress) => + isExist(token.fa2TokenId) ? `${token.contractAddress}${SEPARATOR}${token.fa2TokenId}` : token.contractAddress; + +export const isTezosToken = (token: TokenAddress) => + getTokenSlug(token).toLocaleLowerCase() === getTokenSlug(TEZOS_TOKEN).toLocaleLowerCase(); + +export const toReal = (atomic: BigNumber, decimalsOrToken: Optional) => + atomic.shiftedBy( + -(typeof decimalsOrToken === 'number' ? decimalsOrToken : decimalsOrToken?.metadata.decimals ?? FALLBACK_DECIMALS) + ); + +export const toAtomic = (real: BigNumber, decimalsOrToken: Optional): BigNumber => + real.shiftedBy( + typeof decimalsOrToken === 'number' ? decimalsOrToken : decimalsOrToken?.metadata.decimals ?? FALLBACK_DECIMALS + ); + + +export const getFallbackTokens = (network: QSNetwork) => { + let tokens: Array = [ + { + ...TEZOS_TOKEN, + network: network.id + }, + networksQuipuTokens[network.id] + ]; + return getUniqArray(tokens, getTokenSlug); +}; + +export const isTokenEqual = (a: TokenAddress, b: TokenAddress) => + a.contractAddress === b.contractAddress && a.fa2TokenId === b.fa2TokenId; + +export const getTokens = (network: QSNetwork) => { + let tokens = getFallbackTokens(network); + + const _networkTokens = networkTokens(network.id); + const arr: Token[] = _networkTokens?.tokens?.length ? _networkTokens.tokens.map(token => mapBackendToken(token)) : []; + + if (arr.length) { + tokens = tokens.filter(fallbackToken => !arr.some(token => isTokenEqual(fallbackToken, token))).concat(arr); + } else { + throw new InvalidTokensListError(networkTokens); + } + + return getUniqArray(tokens, getTokenSlug); +}; + +export const mockTezosNow = () => { + return Math.floor(Date.now() / 1000); +}; + +export const dateToSeconds = (date: Date) => { + return Math.floor(date.getTime() / 1000); +}; \ No newline at end of file diff --git a/src/connectors/quipuswap/utils/shared/types.ts b/src/connectors/quipuswap/utils/shared/types.ts new file mode 100644 index 0000000000..084e854b59 --- /dev/null +++ b/src/connectors/quipuswap/utils/shared/types.ts @@ -0,0 +1,96 @@ +import BigNumber from 'bignumber.js'; +import { Trade } from 'swap-router-sdk'; + + +export const NetworkType: Record = { + MAINNET: "mainnet", + GHOSTNET: "ghostnet" +} + +export type Optional = T | null | undefined; +export type Undefined = T | undefined; +export type Nullable = T | null; + +export interface RawToken extends Omit { + type: string; +} + +export type TokenId = Pick; + +export interface TokenAddress { + contractAddress: string; + fa2TokenId?: number; +} + +export enum Standard { + Null = 'Null', + Fa12 = 'FA12', + Fa2 = 'FA2' +} + +export interface TokenMetadata { + decimals: number; + symbol: string; + name: string; +} + +export interface Token extends TokenAddress { + type: Standard; + metadata: TokenMetadata; +} + +export type TokensMap = Map>; + +export type SupportedNetwork = 'mainnet' | 'ghostnet'; + +export enum ConnectType { + DEFAULT = 'DEFAULT', + CUSTOM = 'CUSTOM' +} + +export enum QSNetworkType { + MAIN = 'MAIN', + TEST = 'TEST' +} + +export interface QSNetwork { + id: SupportedNetwork; + connectType: ConnectType; + name: string; + type: QSNetworkType; + disabled: boolean; +} + +export interface TokenWithQSNetworkType extends Token { + network?: SupportedNetwork; +} + +export enum SwapField { + INPUT_AMOUNT = 'inputAmount', + OUTPUT_AMOUNT = 'outputAmount', + INPUT_TOKEN = 'inputToken', + OUTPUT_TOKEN = 'outputToken', + RECIPIENT = 'recipient', +} + +export interface SwapFormValues { + [SwapField.INPUT_TOKEN]: Token; + [SwapField.OUTPUT_TOKEN]: Token; + [SwapField.INPUT_AMOUNT]: BigNumber; + [SwapField.OUTPUT_AMOUNT]: BigNumber; + [SwapField.RECIPIENT]: string; +} + +export interface SwapPair { + inputToken: Token; + outputToken: Token; +} + +export type TradeInfo = { + trade: Trade, + inputToken: Token, + inputAmount: BigNumber, + outputToken: Token, + outputAmount: BigNumber, + price: BigNumber, +}; \ No newline at end of file diff --git a/src/connectors/quipuswap/utils/swap.outputs.ts b/src/connectors/quipuswap/utils/swap.outputs.ts new file mode 100644 index 0000000000..86a4eba762 --- /dev/null +++ b/src/connectors/quipuswap/utils/swap.outputs.ts @@ -0,0 +1,121 @@ +import { BigNumber } from "bignumber.js"; +import { RouteDirectionEnum, TradeOperation, getPairFeeRatio } from "swap-router-sdk"; +import { calculateXToY, calculateYToX, ediv, makeSwapRequiredStorage, newton, performSwap, sumAllFees, util } from "./calculus"; +import { SWAP_RATIO_DENOMINATOR, feeDenominator } from "./config/constants"; +import { DexFeeOverflowError, assertNonNegative } from "./shared/errors"; +import { Nat } from "quipuswap-v3-sdk/dist/types"; + + +export const findFlatCfmmSwapOutput = (aTokenAmount: BigNumber, pair: TradeOperation) => { + let _pair$aTokenMultiplie, _pair$bTokenMultiplie; + + const feeRatio = getPairFeeRatio(pair); + const aTokenMultiplier = (_pair$aTokenMultiplie = pair.aTokenMultiplier) != null ? _pair$aTokenMultiplie : new BigNumber(1); + const bTokenMultiplier = (_pair$bTokenMultiplie = pair.bTokenMultiplier) != null ? _pair$bTokenMultiplie : new BigNumber(1); + const x = pair.aTokenPool.multipliedBy(aTokenMultiplier); + const y = pair.bTokenPool.multipliedBy(bTokenMultiplier); + + const _util2 = util(x, y), u = _util2[0]; + + const p = { + x: x, + y: y, + dx: aTokenAmount.multipliedBy(aTokenMultiplier), + dy: new BigNumber(0), + u: u, + n: 5 + }; + return newton(p).multipliedBy(feeRatio).dividedToIntegerBy(bTokenMultiplier); +}; + + +export const findQuipuCurveLikeSwapOutput = (aTokenAmount: BigNumber, pair: TradeOperation) => { + try { + const tokensInfo = [{ + rate: pair.aTokenMultiplier, + reserves: pair.aTokenPool + }, { + rate: pair.bTokenMultiplier, + reserves: pair.bTokenPool + }]; + + if (pair.cTokenMultiplier) { + tokensInfo.push({ + rate: pair.cTokenMultiplier, + reserves: pair.cTokenPool! + }); + } + + if (pair.dTokenMultiplier) { + tokensInfo.push({ + rate: pair.dTokenMultiplier, + reserves: pair.dTokenPool! + }); + } + + const pool = { + initialA: pair.initialA, + initialATime: pair.initialATime, + futureA: pair.futureA, + futureATime: pair.futureATime, + tokensInfo: tokensInfo, + fee: pair.fees + }; + const dy = performSwap(0, 1, aTokenAmount, pool); + const fee = ediv(sumAllFees(pool.fee!).times(dy), feeDenominator); + return assertNonNegative(dy.minus(fee), new DexFeeOverflowError(dy, fee)); + } catch (e) { + console.error(e); + return new BigNumber(0); + } +}; + +export const findPlentyBridgeSwapOutput = (aTokenAmount: BigNumber) => { + return aTokenAmount; +}; + +export const findSpicyWrapOutput = (aTokenAmount: BigNumber, pair: TradeOperation) => { + let _pair$aTokenMultiplie; + + const swapRatioNumerator = (_pair$aTokenMultiplie = pair.aTokenMultiplier) != null ? _pair$aTokenMultiplie : new BigNumber(0); + const feeRatio = getPairFeeRatio(pair); + + if (pair.direction === RouteDirectionEnum.Direct) { + const swapRatio = swapRatioNumerator.dividedBy(SWAP_RATIO_DENOMINATOR); + return aTokenAmount.multipliedBy(swapRatio).dividedToIntegerBy(1); + } else { + const _swapRatio = SWAP_RATIO_DENOMINATOR.dividedBy(swapRatioNumerator).multipliedBy(feeRatio); + + return aTokenAmount.multipliedBy(_swapRatio).dividedToIntegerBy(1); + } +}; + +export const findQuipuSwapV3Output = (aTokenAmount: BigNumber, pair: TradeOperation) => { + try { + const direction = pair.direction; + const calculationFunction = direction === RouteDirectionEnum.Direct ? calculateXToY : calculateYToX; + + const _calculationFunction = calculationFunction(makeSwapRequiredStorage(pair), new Nat(aTokenAmount.integerValue(Nat.ROUND_DOWN))), + output = _calculationFunction.output; + + return output; + } catch (e) { + return new Nat(0); + } +}; + +export const findAmmSwapOutput = (aTokenAmount: BigNumber, pair: TradeOperation) => { + const feeRatio = getPairFeeRatio(pair); + const aTokenAmountWithFee = aTokenAmount.times(feeRatio); + const numerator = aTokenAmountWithFee.times(pair.bTokenPool); + const denominator = pair.aTokenPool.plus(aTokenAmountWithFee); + return numerator.idiv(denominator); +}; + +export const findAmmSwapInput = (bTokenAmount: BigNumber, pair: TradeOperation) => { + const feeRatio = getPairFeeRatio(pair); + const numerator = pair.aTokenPool.times(bTokenAmount); + const denominator = pair.bTokenPool.minus(bTokenAmount).times(feeRatio); + const input = numerator.idiv(denominator).plus(1); + return input.isGreaterThan(0) ? input : new BigNumber(Infinity); +}; \ No newline at end of file diff --git a/src/connectors/quipuswap/utils/swap.router.sdk.adapters.ts b/src/connectors/quipuswap/utils/swap.router.sdk.adapters.ts new file mode 100644 index 0000000000..d099765eb3 --- /dev/null +++ b/src/connectors/quipuswap/utils/swap.router.sdk.adapters.ts @@ -0,0 +1,48 @@ +import { + TokenStandardEnum, + getAllowedRoutePairsCombinations as originalGetAllowedRoutePairsCombinations +} from "swap-router-sdk"; +import { RoutePair } from "swap-router-sdk/dist/interface/route-pair.interface"; +import { getTokenIdFromSlug, getTokenSlug, isExist, isTezosToken } from "./shared/helpers"; +import { SwapPair, Token } from "./shared/types"; +import { MAX_HOPS_COUNT } from "./config/constants"; +import { WhitelistedPair } from "swap-router-sdk/dist/interface/whitelisted-pair.interface"; + +const FALLBACK_TOKEN_ID = 0; + + +export const swapRouterSdkTokenSlugToQuipuTokenSlug = (inputSlug: string, tokenStandard?: TokenStandardEnum) => { + const { contractAddress, fa2TokenId } = getTokenIdFromSlug(inputSlug); + + if (isExist(fa2TokenId)) { + return getTokenSlug({ + contractAddress, + fa2TokenId: tokenStandard === TokenStandardEnum.FA2 ? fa2TokenId : undefined + }); + } + + return inputSlug; +}; + + +export const getSwapRouterSdkTokenSlug = (token: Token) => + getTokenSlug({ + ...token, + fa2TokenId: isTezosToken(token) ? undefined : token.fa2TokenId ?? FALLBACK_TOKEN_ID + }); + + +export const getRoutePairsCombinations = ( + swapPair: SwapPair, + routePairs: RoutePair[], + whitelistedPairs: WhitelistedPair[] +) => { + const { inputToken, outputToken } = swapPair; + return originalGetAllowedRoutePairsCombinations( + inputToken ? getSwapRouterSdkTokenSlug(inputToken) : undefined, + outputToken ? getSwapRouterSdkTokenSlug(outputToken) : undefined, + routePairs, + whitelistedPairs, + MAX_HOPS_COUNT + ); +}; diff --git a/src/connectors/quipuswap/utils/trade.ts b/src/connectors/quipuswap/utils/trade.ts new file mode 100644 index 0000000000..9fe7a59a5c --- /dev/null +++ b/src/connectors/quipuswap/utils/trade.ts @@ -0,0 +1,118 @@ +import BigNumber from "bignumber.js"; +import { DexTypeEnum, Trade, TradeOperation } from "swap-router-sdk"; +import { findAmmSwapInput, findAmmSwapOutput, findFlatCfmmSwapOutput, findPlentyBridgeSwapOutput, findQuipuCurveLikeSwapOutput, findQuipuSwapV3Output, findSpicyWrapOutput } from "./swap.outputs"; + + +const findSwapOutput = (aTokenAmount: BigNumber, pair: TradeOperation) => { + switch (pair.dexType) { + case DexTypeEnum.Youves: + case DexTypeEnum.PlentyStableSwap: + case DexTypeEnum.PlentyCtez: + return findFlatCfmmSwapOutput(aTokenAmount, pair); + + case DexTypeEnum.QuipuSwapCurveLike: + return findQuipuCurveLikeSwapOutput(aTokenAmount, pair); + + case DexTypeEnum.PlentyBridge: + return findPlentyBridgeSwapOutput(aTokenAmount); + + case DexTypeEnum.SpicyWrap: + return findSpicyWrapOutput(aTokenAmount, pair); + + case DexTypeEnum.QuipuSwapV3: + return findQuipuSwapV3Output(aTokenAmount, pair); + + case DexTypeEnum.YupanaWtez: + return aTokenAmount; + + default: + return findAmmSwapOutput(aTokenAmount, pair); + } +}; + +const getTradeOperationExactInput = (aTokenAmount: BigNumber, pair: TradeOperation, slippageToleranceRatio: number) => ({ + ...pair, + aTokenAmount: aTokenAmount.integerValue(BigNumber.ROUND_DOWN), + bTokenAmount: findSwapOutput(BigNumber(aTokenAmount).multipliedBy(slippageToleranceRatio), pair) +}); + +export const calculateTradeExactInput = (inputAssetAmount: BigNumber, routePairs: Trade, slippageTolerancePercent: number) => { + if (slippageTolerancePercent === void 0) { + slippageTolerancePercent = 0; + } + + if (routePairs.length === 0) { + return []; + } + + const trade = []; + const slippageToleranceRatio = (100 - slippageTolerancePercent) / 100; + const tradeOperationSlippageToleranceRatio = Math.pow(slippageToleranceRatio, 1 / routePairs.length); + let currentInput = inputAssetAmount; + + for (let i = 0; i < routePairs.length; i++) { + const tradeOperation = getTradeOperationExactInput(currentInput, routePairs[i], tradeOperationSlippageToleranceRatio); + + if (tradeOperation.bTokenAmount.isNegative()) { + return []; + } + + trade.push(tradeOperation); + currentInput = tradeOperation.bTokenAmount; + } + + return trade; +}; + +const findSwapInput = (bTokenAmount: BigNumber, pair: TradeOperation) => { + switch (pair.dexType) { + case DexTypeEnum.Youves: + case DexTypeEnum.PlentyStableSwap: + case DexTypeEnum.PlentyCtez: + // return findFlatCfmmSwapInput(bTokenAmount, pair); + return BigNumber(Infinity); + + case DexTypeEnum.QuipuSwapCurveLike: + // return findQuipuCurveLikeSwapInput(bTokenAmount, pair); + return new BigNumber(Infinity); + + case DexTypeEnum.PlentyBridge: + return new BigNumber(0); + + case DexTypeEnum.QuipuSwapV3: + // return findQuipuSwapV3Input(bTokenAmount, pair); + return new BigNumber(Infinity); + + case DexTypeEnum.YupanaWtez: + return bTokenAmount; + + default: + return findAmmSwapInput(bTokenAmount, pair); + } +}; + +const getTradeOperationExactOutput = (bTokenAmount: BigNumber, pair: TradeOperation) => ({ + ...pair, + bTokenAmount: bTokenAmount, + aTokenAmount: findSwapInput(bTokenAmount, pair) +}); + +export const calculateTradeExactOutput = (outputAssetAmount: BigNumber, routePairs: Trade) => { + const trade = []; + + if (routePairs.length > 0) { + const lastTradeIndex = routePairs.length - 1; + const firstTradeOperation = getTradeOperationExactOutput(outputAssetAmount, routePairs[lastTradeIndex]); + trade.unshift(firstTradeOperation); + + if (routePairs.length > 1) { + for (let i = lastTradeIndex - 1; i >= 0; i--) { + const previousTradeInput = trade[0].aTokenAmount; + const tradeOperation = getTradeOperationExactOutput(previousTradeInput, routePairs[i]); + trade.unshift(tradeOperation); + } + } + } + + return trade; +}; \ No newline at end of file diff --git a/src/connectors/ref/ref.controllers.ts b/src/connectors/ref/ref.controllers.ts index b82e104760..fd64d2027c 100644 --- a/src/connectors/ref/ref.controllers.ts +++ b/src/connectors/ref/ref.controllers.ts @@ -187,7 +187,7 @@ export async function trade( logger.info( `Expected execution price is ${estimatedPrice}, ` + - `limit price is ${limitPrice}.` + `limit price is ${limitPrice}.` ); if (req.side === 'BUY') { diff --git a/src/services/common-interfaces.ts b/src/services/common-interfaces.ts index b42d0fc16d..25e8ac78ce 100644 --- a/src/services/common-interfaces.ts +++ b/src/services/common-interfaces.ts @@ -81,9 +81,13 @@ import { import { Token as PancakeSwapToken, CurrencyAmount as PancakeSwapCurrencyAmount, + TradeType as PancakeSwapTradeType, Trade as PancakeSwapTrade, Fraction as PancakeSwapFraction, + Currency as PancakeSwapCurrency, + Price as PancakeSwapPrice, } from '@pancakeswap/sdk'; +import { SmartRouterTrade as PancakeSwapSmartRouterTrade } from '@pancakeswap/smart-router'; import { Token as TokenXsswap, CurrencyAmount as CurrencyAmountXsswap, @@ -141,7 +145,17 @@ export type UniswapishTrade = | TradeTraderjoe | SushiswapTrade | TradeUniswap - | PancakeSwapTrade + | PancakeSwapTrade< + PancakeSwapCurrency, + PancakeSwapCurrency, + PancakeSwapTradeType + > + | (PancakeSwapSmartRouterTrade & { + executionPrice: PancakeSwapPrice< + PancakeSwapCurrency, + PancakeSwapCurrency + >; + }) | MMFTrade | VVSTrade | TradeXsswap @@ -163,7 +177,7 @@ export type UniswapishAmount = | UniswapCoreCurrencyAmount | CurrencyAmountTraderjoe | SushiCurrencyAmount - | PancakeSwapCurrencyAmount + | PancakeSwapCurrencyAmount | CurrencyAmountMMF | CurrencyAmountVVS | CurrencyAmountXsswap diff --git a/src/services/connection-manager.ts b/src/services/connection-manager.ts index 2000e494e4..f2c1c11b36 100644 --- a/src/services/connection-manager.ts +++ b/src/services/connection-manager.ts @@ -42,6 +42,7 @@ import { Kujira } from '../chains/kujira/kujira'; import { KujiraCLOB } from '../connectors/kujira/kujira'; import { PancakeswapLP } from '../connectors/pancakeswap/pancakeswap.lp'; import { XRPLCLOB } from '../connectors/xrpl/xrpl'; +import { QuipuSwap } from '../connectors/quipuswap/quipuswap'; export type ChainUnion = | Algorand @@ -149,7 +150,8 @@ export type ConnectorUnion = | Plenty | XRPLCLOB | Curve - | KujiraCLOB; + | KujiraCLOB + | QuipuSwap; export type Connector = T extends Uniswapish ? Uniswapish @@ -169,6 +171,8 @@ export type Connector = T extends Uniswapish ? XRPLCLOB : T extends KujiraCLOB ? KujiraCLOB + : T extends QuipuSwap + ? QuipuSwap : never; export async function getConnector( @@ -228,7 +232,10 @@ export async function getConnector( connector === 'curve' ) { connectorInstance = Curve.getInstance(chain, network); - } else { + } else if (chain === 'tezos' && connector === 'quipuswap') { + connectorInstance = QuipuSwap.getInstance(network); + } + else { throw new Error('unsupported chain or connector'); } diff --git a/src/services/schema/quipuswap-schema.json b/src/services/schema/quipuswap-schema.json new file mode 100644 index 0000000000..5636c480a0 --- /dev/null +++ b/src/services/schema/quipuswap-schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "allowedSlippage": { + "type": "string" + }, + "gasLimitEstimate": { + "type": "integer" + }, + "network": { + "type": "object", + "patternProperties": { + "^\\w+$": { + "type": "object", + "properties": { + "apiUrl": { + "type": "string" + } + }, + "required": [ + "apiUrl" + ], + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [ + "network", + "allowedSlippage", + "gasLimitEstimate" + ] +} \ No newline at end of file diff --git a/src/templates/avalanche.yml b/src/templates/avalanche.yml index 025b3b6a6b..852e172c80 100644 --- a/src/templates/avalanche.yml +++ b/src/templates/avalanche.yml @@ -18,7 +18,7 @@ networks: chainID: 432204 nodeURL: https://subnets.avax.network/dexalot/mainnet/rpc tokenListType: 'FILE' - tokenListSource: '/home/gateway/conf/lists/avalanche_tokens.json' + tokenListSource: '/home/gateway/conf/lists/dexalot_tokens.json' nativeCurrencySymbol: 'ALOT' gasPriceRefreshInterval: 60 diff --git a/src/templates/binance-smart-chain.yml b/src/templates/binance-smart-chain.yml index 7de9f60477..e85f485640 100644 --- a/src/templates/binance-smart-chain.yml +++ b/src/templates/binance-smart-chain.yml @@ -2,14 +2,14 @@ networks: mainnet: chainID: 56 - nodeURL: 'https://rpc.ankr.com/bsc' + nodeURL: 'https://binance.llamarpc.com' tokenListType: FILE tokenListSource: /home/gateway/conf/lists/bep20_tokens_mainnet.json nativeCurrencySymbol: 'BNB' gasPriceRefreshInterval: 60 testnet: chainID: 97 - nodeURL: 'https://rpc.ankr.com/bsc_testnet_chapel' + nodeURL: 'https://bsc-testnet.publicnode.com' tokenListType: 'FILE' tokenListSource: '/home/gateway/conf/lists/bep20_tokens_testnet.json' nativeCurrencySymbol: 'BNB' diff --git a/src/templates/kujira.yml b/src/templates/kujira.yml index dd691b8b50..8fbeeccf84 100644 --- a/src/templates/kujira.yml +++ b/src/templates/kujira.yml @@ -37,7 +37,7 @@ networks: tokenListSource: /home/gateway/conf/lists/kujira.json prefix: 'kujira' accountNumber: 0 -gasPrice: 0.00125 +gasPrice: 0.0034 gasPriceSuffix: 'ukuji' gasLimitEstimate: 0.009147 orderBook: diff --git a/src/templates/lists/dexalot_tokens.json b/src/templates/lists/dexalot_tokens.json new file mode 100644 index 0000000000..f43d486926 --- /dev/null +++ b/src/templates/lists/dexalot_tokens.json @@ -0,0 +1,169 @@ +{ + "name": "Pangolin Tokenlist", + "logoURI": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/432204/0x60781C2586D68229fde47564546784ab3fACA982/logo_48.png", + "keywords": [], + "version": { + "major": 3, + "minor": 11, + "patch": 1 + }, + "timestamp": "2022-12-15T12:00:00+00:00", + "tokens": [ + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "ALOT", + "name": "Dexalot Token", + "isnative": false, + "address": "0x093783055F9047C2BfF99c4e414501F8A147bC69", + "decimals": 18, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "1.9" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "AVAX", + "name": "Avalanche", + "isnative": false, + "address": "0x0000000000000000000000000000000000000000", + "decimals": 18, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "0.0246467720588235293" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "BTC.b", + "name": "Bitcoin", + "isnative": false, + "address": "0x152b9d0FdC40C096757F570A51E494bd4b943E50", + "decimals": 8, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "0.000012578" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "COQ", + "name": "COQ", + "isnative": false, + "address": "0x420FcA0121DC28039145009570975747295f2329", + "decimals": 18, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "5700000.0" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "EUROC", + "name": "EUROC", + "isnative": false, + "address": "0xC891EB4cbdEFf6e073e859e987815Ed1505c2ACD", + "decimals": 6, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "0.342" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "LOST", + "name": "LostToken", + "isnative": false, + "address": "0x449674B82F05d498E126Dd6615a1057A9c088f2C", + "decimals": 18, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "6.1328435005156812124" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "QI", + "name": "QI", + "isnative": false, + "address": "0x8729438EB15e2C8B576fCc6AeCdA6A148776C0F5", + "decimals": 18, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "190.0" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "sAVAX", + "name": "sAVAX", + "isnative": false, + "address": "0x2b2C81e08f1Af8835a78Bb2A90AE924ACE0eA4bE", + "decimals": 18, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "0.0247" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "STL", + "name": "Cyberstella", + "isnative": false, + "address": "0x66B071A55b7c258c2086527e35EE355771aA05B8", + "decimals": 18, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "22.8" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "USDC", + "name": "USD Coin", + "isnative": false, + "address": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", + "decimals": 6, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "0.3583419" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "USDt", + "name": "Tether USD", + "isnative": false, + "address": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", + "decimals": 6, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "0.3582545" + }, + { + "env": "production-multi-mainnet", + "chainId": 432204, + "symbol": "WETH.e", + "name": "Wrapped Ether", + "isnative": false, + "address": "0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB", + "decimals": 18, + "status": "deployed", + "auctionmode": 0, + "auctionendtime": null, + "min_depositamnt": "0.0001874706051707260" + } + ] +} diff --git a/src/templates/lists/tezos.ghostnet.tokens.json b/src/templates/lists/tezos.ghostnet.tokens.json index f0dd3cd592..3477a1b651 100644 --- a/src/templates/lists/tezos.ghostnet.tokens.json +++ b/src/templates/lists/tezos.ghostnet.tokens.json @@ -63,6 +63,94 @@ "decimals": 6, "standard": "FA2", "tokenId": 2 + }, + { + "name": "Tezos", + "symbol": "XTZ", + "address": null, + "decimals": 6, + "standard": "TEZ", + "tokenId": null + }, + { + "name": "Apple Token", + "symbol": "Apple", + "address": "KT1BRxbKjhRabGSB6saqK6QrBenaSvFJSgHu", + "decimals": 10, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "GOLD Token", + "symbol": "GOLD", + "address": "KT1PzyU2nXYW8RkoFqmqWPFCA7bgC7yGNRoC", + "decimals": 12, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "Test youves BTC", + "symbol": "uBTC", + "address": "KT1N4NfnYmJucXYkuPdvJG4Jxbz3TetCTqJc", + "decimals": 12, + "standard": "FA2", + "tokenId": 2 + }, + { + "name": "Test tzBTC", + "symbol": "tzBTC", + "address": "KT1Wdq6sj3ZkNqQ7CeE6kTNbJXfobMX7Eqpz", + "decimals": 8, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "Test youves USD", + "symbol": "uUSD", + "address": "KT1N4NfnYmJucXYkuPdvJG4Jxbz3TetCTqJc", + "decimals": 12, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Test Kolibri USD", + "symbol": "kUSD", + "address": "KT1GG8Zd5rUp1XV8nMPRBY2tSyVn6NR5F4Q1", + "decimals": 18, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "Test USDtez", + "symbol": "USDtz", + "address": "KT1QzmrMs1xUXZJ8TPAoDEFaKC6w56RfdLWo", + "decimals": 6, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "Ethereum USDC", + "symbol": "USDC.e", + "address": "KT1A4W7CfD5sKkWXJke392jVmxSA6vaHrdWk", + "decimals": 6, + "standard": "FA2", + "tokenId": 2 + }, + { + "name": "Quipuswap Governance Token", + "symbol": "QUIPU", + "address": "KT19363aZDTjeRyoDkSLZhCk62pS4xfvxo6c", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Wrapped Tezos FA2 token", + "symbol": "wTEZ", + "address": "KT1L8ujeb25JWKa4yPB61ub4QG2NbaKfdJDK", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 } ] } \ No newline at end of file diff --git a/src/templates/lists/tezos.mainnet.tokens.json b/src/templates/lists/tezos.mainnet.tokens.json index f200c6fe38..d33c841727 100644 --- a/src/templates/lists/tezos.mainnet.tokens.json +++ b/src/templates/lists/tezos.mainnet.tokens.json @@ -519,6 +519,382 @@ "standard": "FA2", "address": "KT1GY5qCWwmESfTv9dgjYyTYs2T5XGDSvRp1", "tokenId": 0 + }, + { + "name": "Stably USD", + "symbol": "USDS", + "address": "KT1REEb5VxWRjcHm5GzDMwErMmNFftsE5Gpf", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Staker Governance Token", + "symbol": "STKR", + "address": "KT1AEfeckNbdEYwaMKkytBwPJPycz7jdSGea", + "decimals": 18, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "hic et nunc DAO", + "symbol": "hDAO", + "address": "KT1AFA2mwNUMNd4SsujE1YYp29vd8BZejyKW", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Wrap Governance Token", + "symbol": "WRAP", + "address": "KT1LRboPna9yQY9BrjtQYDS1DVxhKESK4VVd", + "decimals": 8, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "CRUNCH", + "symbol": "CRUNCH", + "address": "KT1BHCumksALJQJ8q8to2EPigPW6qpyTr7Ng", + "decimals": 8, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Wrapped AAVE", + "symbol": "wAAVE", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Wrapped BUSD", + "symbol": "wBUSD", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 1 + }, + { + "name": "Wrapped CEL", + "symbol": "wCEL", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 4, + "standard": "FA2", + "tokenId": 2 + }, + { + "name": "Wrapped COMP", + "symbol": "wCOMP", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 3 + }, + { + "name": "Wrapped CRO", + "symbol": "wCRO", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 8, + "standard": "FA2", + "tokenId": 4 + }, + { + "name": "Wrapped DAI", + "symbol": "wDAI", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 5 + }, + { + "name": "Wrapped FTT", + "symbol": "wFTT", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 6 + }, + { + "name": "Wrapped HT", + "symbol": "wHT", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 7 + }, + { + "name": "Wrapped HUSD", + "symbol": "wHUSD", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 8, + "standard": "FA2", + "tokenId": 8 + }, + { + "name": "Wrapped LEO", + "symbol": "wLEO", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 9 + }, + { + "name": "Wrapped LINK", + "symbol": "wLINK", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 10 + }, + { + "name": "Wrapped MATIC", + "symbol": "wMATIC", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 11 + }, + { + "name": "Wrapped MKR", + "symbol": "wMKR", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 12 + }, + { + "name": "Wrapped OKB", + "symbol": "wOKB", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 13 + }, + { + "name": "Wrapped PAX", + "symbol": "wPAX", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 14 + }, + { + "name": "Wrapped SUSHI", + "symbol": "wSUSHI", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 15 + }, + { + "name": "Wrapped UNI", + "symbol": "wUNI", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 16 + }, + { + "name": "Wrapped USDC", + "symbol": "wUSDC", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 6, + "standard": "FA2", + "tokenId": 17 + }, + { + "name": "Wrapped USDT", + "symbol": "wUSDT", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 6, + "standard": "FA2", + "tokenId": 18 + }, + { + "name": "Wrapped WETH", + "symbol": "wWETH", + "address": "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", + "decimals": 18, + "standard": "FA2", + "tokenId": 20 + }, + { + "name": "Plenty DAO", + "symbol": "PLENTY", + "address": "KT1GRSvLoikDsXujKgZPsGLX8k8VvR2Tq95b", + "decimals": 18, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "Crunchy DAO", + "symbol": "crDAO", + "address": "KT1XPFjZqCULSnqfKaaYy8hJjeY63UNSGwXg", + "decimals": 8, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Bazaar DAO", + "symbol": "bDAO", + "address": "KT1GUNKmkrgtMQjJp3XxcmCj6HZBhkUmMbge", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Rocket", + "symbol": "RCKT", + "address": "KT19JYndHaesXpvUfiwgg8BtE41HKkjjGMRC", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Rocket DAO", + "symbol": "rkDAO", + "address": "KT1MuyJ7gVw74FNJpfb2mHR15aCREdyEbe2e", + "decimals": 8, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "TezID", + "symbol": "IDZ", + "address": "KT1WapdVeFqhCfqwdHWwTzSTX7yXoHgiPRPU", + "decimals": 8, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "CryptoEasy", + "symbol": "EASY", + "address": "KT1QgAtLPu3SNq9c6DPLanwL5bvfX3rgh2CS", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "xPLENTY", + "symbol": "xPLENTY", + "address": "KT1Rpviewjg82JgjGfAKFneSupjAR1kUhbza", + "decimals": 18, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "Spice Token", + "symbol": "SPI", + "address": "KT1CS2xKGHNPTauSh5Re4qE3N9PCfG5u4dPx", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Pixel", + "symbol": "PXL", + "address": "KT1F1mn2jbqQCJcsNgYKVAQjvenecNMY2oPK", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "akaSwap DAO", + "symbol": "akaDAO", + "address": "KT1AM3PV1cwmGRw28DVTgsjjsjHvmL6z4rGh", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Ethereum DAI", + "symbol": "DAI.e", + "address": "KT1UsSfaXyqcjSVPeiD7U1bWgKy3taYN7NWY", + "decimals": 18, + "standard": "FA2", + "tokenId": 6 + }, + { + "name": "Allbridge", + "symbol": "ABR", + "address": "KT1UG6PdaKoJcc3yD6mkFVfxnS1uJeW3cGeX", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Allbridge Wrapped BUSD", + "symbol": "abBUSD", + "address": "KT1UG6PdaKoJcc3yD6mkFVfxnS1uJeW3cGeX", + "decimals": 6, + "standard": "FA2", + "tokenId": 1 + }, + { + "name": "Allbridge Wrapped Polygon USDC", + "symbol": "apUSDC", + "address": "KT1UG6PdaKoJcc3yD6mkFVfxnS1uJeW3cGeX", + "decimals": 6, + "standard": "FA2", + "tokenId": 2 + }, + { + "name": "Old Temple Key", + "symbol": "Old TKEY", + "address": "KT1WihWRnmzhfebi6zqQ4tvNGiPeVxiGwTi2", + "decimals": 18, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Temple Key", + "symbol": "TKEY", + "address": "KT1VaEsVNiBoA56eToEK6n6BcPgh1tdx9eXi", + "decimals": 18, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "STARtz", + "symbol": "STARtz", + "address": "KT1GS3uEWWVkAxVfq66EtUn1g6uGX1J72K5Y", + "decimals": 0, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "TCOIN", + "symbol": "TCOIN", + "address": "KT1GorwGk5WXLUc3sEWrmPLQBSekmYrtz1sn", + "decimals": 8, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "Teia DAO token", + "symbol": "TEIA", + "address": "KT1QrtA753MSv8VGxkDrKKyJniG5JtuHHbtV", + "decimals": 6, + "standard": "FA2", + "tokenId": 0 + }, + { + "name": "Kord.Fi tez deposit", + "symbol": "dtez", + "address": "KT19qWdPBRtkWrsQnDvVfsqJgJB19keBhhMX", + "decimals": 12, + "standard": "FA1.2", + "tokenId": 0 + }, + { + "name": "Kord.Fi tzBTC deposit", + "symbol": "dtzbtc", + "address": "KT1WL6sHt8syFT2ts7NCmb5gPcS2tyfRxSyi", + "decimals": 20, + "standard": "FA1.2", + "tokenId": 0 } ] } \ No newline at end of file diff --git a/src/templates/pancakeswap.yml b/src/templates/pancakeswap.yml index d0f730c2d1..ec0a198055 100644 --- a/src/templates/pancakeswap.yml +++ b/src/templates/pancakeswap.yml @@ -25,12 +25,12 @@ feeTier: 'MEDIUM' contractAddresses: mainnet: - routerAddress: '0x10ED43C718714eb63d5aA57B78B54704E256024E' + routerAddress: '0x13f4EA83D0bd40E75C8222255bc855a974568Dd4' pancakeswapV3SmartOrderRouterAddress: '0x1b81D678ffb9C0263b24A97847620C99d213eB14' pancakeswapV3NftManagerAddress: '0x46A15B0b27311cedF172AB29E4f4766fbE7F4364' pancakeswapV3QuoterV2ContractAddress: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997' testnet: - routerAddress: '0xdc4904b5f716Ff30d8495e35dC99c109bb5eCf81' + routerAddress: '0x9a489505a00cE272eAa5e07Dba6491314CaE3796' pancakeswapV3SmartOrderRouterAddress: '0x1b81D678ffb9C0263b24A97847620C99d213eB14' pancakeswapV3NftManagerAddress: '0x427bF5b37357632377eCbEC9de3626C71A5396c1' pancakeswapV3QuoterV2ContractAddress: '0xbC203d7f83677c7ed3F7acEc959963E7F4ECC5C2' diff --git a/src/templates/quipuswap.yml b/src/templates/quipuswap.yml new file mode 100644 index 0000000000..8ccbf55514 --- /dev/null +++ b/src/templates/quipuswap.yml @@ -0,0 +1,14 @@ +# allowedSlippage: how much the execution price is allowed to move unfavorably from the trade +# execution price. It uses a rational number for precision. +allowedSlippage: '1/200' + + +# the maximum gas used to estimate gasCost for a trade. +gasLimitEstimate: 15000 + + +network: + mainnet: + apiUrl: 'wss://dexes-api-mainnet.prod.templewallet.com/' + ghostnet: + apiUrl: 'wss://dexes-api-ghostnet.prod.templewallet.com/' diff --git a/src/templates/root.yml b/src/templates/root.yml index 39736c89cc..1a2f2a51e2 100644 --- a/src/templates/root.yml +++ b/src/templates/root.yml @@ -119,3 +119,7 @@ configurations: $namespace kujira: configurationPath: kujira.yml schemaPath: kujira-schema.json + + $namespace quipuswap: + configurationPath: quipuswap.yml + schemaPath: quipuswap-schema.json \ No newline at end of file diff --git a/test-bronze/connectors/quipuswap/quipuswap.routes.test.ts b/test-bronze/connectors/quipuswap/quipuswap.routes.test.ts new file mode 100644 index 0000000000..ebd76d39a9 --- /dev/null +++ b/test-bronze/connectors/quipuswap/quipuswap.routes.test.ts @@ -0,0 +1,435 @@ +import { BigNumber } from 'bignumber.js'; +import express from 'express'; +import { Express } from 'express-serve-static-core'; +import request from 'supertest'; +import { AmmRoutes } from '../../../src/amm/amm.routes'; +import { patch, unpatch } from '../../../test/services/patch'; +import { Tezos } from '../../../src/chains/tezos/tezos'; +import { QuipuSwap } from '../../../src/connectors/quipuswap/quipuswap'; +let app: Express; +let tezos: Tezos; +let quipuswap: QuipuSwap; + + +beforeAll(async () => { + app = express(); + app.use(express.json()); + + tezos = Tezos.getInstance('mainnet'); + await tezos.init(); + quipuswap = QuipuSwap.getInstance('mainnet'); + + + app.use('/amm', AmmRoutes.router); +}); + +afterEach(() => { + unpatch(); +}); + +afterAll(async () => { + await tezos.close(); +}); + +const address: string = 'tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV'; + +const patchGetWallet = () => { + patch(tezos, 'getWallet', () => { + return { + signer: { + publicKeyHash: () => 'tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV' + }, + estimate: { + batch: () => [ + { + totalCost: 100, + gasLimit: 100, + }, + { + totalCost: 200, + gasLimit: 200, + } + ] + } + }; + }); +}; + +const patchGasPrice = () => { + patch(tezos, 'gasPrice', () => 123456); +}; + +const patchEstimateBuyTrade = () => { + patch(quipuswap, 'estimateBuyTrade', () => { + return { + trade: [], + inputAmount: new BigNumber(1000000), + outputAmount: new BigNumber(1000000), + price: new BigNumber(1), + }; + }); +}; + +const patchEstimateSellTrade = () => { + patch(quipuswap, 'estimateSellTrade', () => { + return { + trade: [], + inputAmount: new BigNumber(1000000), + outputAmount: new BigNumber(1000000), + price: new BigNumber(1), + }; + }); +}; + +const patchExecuteTrade = () => { + patch(quipuswap, 'executeTrade', () => { + return { hash: '000000000000000', operations: [{ counter: 21 }] }; + }); +}; + +describe('POST /amm/price', () => { + it('should return 200 for BUY', async () => { + patchGetWallet(); + patchGasPrice(); + patchEstimateBuyTrade(); + patchExecuteTrade(); + + await request(app) + .post(`/amm/price`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + side: 'BUY', + }) + .set('Accept', 'application/json') + .expect(200) + .then((res: any) => { + expect(res.body.amount).toEqual('1.000000'); + expect(res.body.rawAmount).toEqual('1000000'); + }); + }); + + it('should return 200 for SELL', async () => { + patchGetWallet(); + patchGasPrice(); + patchEstimateSellTrade(); + patchExecuteTrade(); + + await request(app) + .post(`/amm/price`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + side: 'SELL', + }) + .set('Accept', 'application/json') + .expect(200) + .then((res: any) => { + expect(res.body.amount).toEqual('1.000000'); + expect(res.body.rawAmount).toEqual('1000000'); + }); + }); + + it('should return 500 for unrecognized quote symbol', async () => { + patchGetWallet(); + patchEstimateSellTrade(); + + await request(app) + .post(`/amm/price`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: '$', + base: 'XTZ', + amount: '1', + side: 'SELL', + }) + .set('Accept', 'application/json') + .expect(500); + }); + + it('should return 500 for unrecognized base symbol', async () => { + patchGetWallet(); + patchEstimateSellTrade(); + + await request(app) + .post(`/amm/price`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: '$', + amount: '1', + side: 'SELL', + }) + .set('Accept', 'application/json') + .expect(500); + }); + + it('should return 500 when the estimateSellTrade operation fails', async () => { + patchGetWallet(); + patchEstimateSellTrade(); + + patch(quipuswap, 'estimateSellTrade', () => { + return 'error'; + }); + + await request(app) + .post(`/amm/price`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + side: 'SELL', + }) + .set('Accept', 'application/json') + .expect(500); + }); +}); + +describe('POST /amm/trade', () => { + const patchForBuy = () => { + patchGetWallet(); + patchGasPrice(); + patchEstimateBuyTrade(); + patchExecuteTrade(); + }; + + it('should return 200 for BUY', async () => { + patchForBuy(); + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + address, + side: 'BUY', + }) + .set('Accept', 'application/json') + .expect(200) + .then((res: any) => { + expect(res.body.nonce).toEqual(21); + }); + }); + + const patchForSell = () => { + patchGetWallet(); + patchGasPrice(); + patchEstimateSellTrade(); + patchExecuteTrade(); + }; + + it('should return 200 for SELL', async () => { + patchForSell(); + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + address, + side: 'SELL', + }) + .set('Accept', 'application/json') + .expect(200) + .then((res: any) => { + expect(res.body.nonce).toEqual(21); + }); + }); + + it('should return 200 for SELL with limitPrice', async () => { + patchForSell(); + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + address, + side: 'SELL', + limitPrice: '1', + }) + .set('Accept', 'application/json') + .expect(200); + }); + + it('should return 200 for BUY with limitPrice', async () => { + patchForBuy(); + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + address, + side: 'BUY', + limitPrice: '999999999999999999999', + }) + .set('Accept', 'application/json') + .expect(200); + }); + + it('should return 500 for BUY with price greater than limitPrice', async () => { + patchForBuy(); + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + address, + side: 'BUY', + limitPrice: '0.9', + }) + .set('Accept', 'application/json') + .expect(500); + }); + + it('should return 500 for SELL with price lower than limitPrice', async () => { + patchForSell(); + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + address, + side: 'SELL', + limitPrice: '99999999999', + }) + .set('Accept', 'application/json') + .expect(500); + }); + + it('should return 404 when parameters are incorrect', async () => { + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: 1, + address: 'da8', + side: 'comprar', + }) + .set('Accept', 'application/json') + .expect(404); + }); + + it('should return 500 when the routerSwap operation fails', async () => { + patchGetWallet(); + patch(quipuswap, 'routerSwap', () => { + return 'error'; + }); + + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + address, + side: 'SELL', + maxFeePerGas: '5000000000', + maxPriorityFeePerGas: '5000000000', + }) + .set('Accept', 'application/json') + .expect(500); + }); + + it('should return 500 when the priceSwapOut operation fails', async () => { + patchGetWallet(); + patch(quipuswap, 'priceSwapOut', () => { + return 'error'; + }); + + await request(app) + .post(`/amm/trade`) + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + quote: 'USDT', + base: 'XTZ', + amount: '1', + address, + side: 'BUY', + maxFeePerGas: '5000000000', + maxPriorityFeePerGas: '5000000000', + }) + .set('Accept', 'application/json') + .expect(500); + }); +}); + +describe('POST /amm/estimateGas', () => { + it('should return 200 for valid connector', async () => { + patchGasPrice(); + + await request(app) + .post('/amm/estimateGas') + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'quipuswap', + }) + .set('Accept', 'application/json') + .expect(200) + .then((res: any) => { + expect(res.body.network).toEqual('mainnet'); + expect(res.body.gasPrice).toEqual(0.123456); + expect(res.body.gasCost).toEqual('0.001852'); + }); + }); + + it('should return 500 for invalid connector', async () => { + patchGasPrice(); + + await request(app) + .post('/amm/estimateGas') + .send({ + chain: 'tezos', + network: 'mainnet', + connector: 'pangolin', + }) + .set('Accept', 'application/json') + .expect(500); + }); +}); diff --git a/test-bronze/connectors/quipuswap/quipuswap.test.ts b/test-bronze/connectors/quipuswap/quipuswap.test.ts new file mode 100644 index 0000000000..d174b14a54 --- /dev/null +++ b/test-bronze/connectors/quipuswap/quipuswap.test.ts @@ -0,0 +1,93 @@ +import BigNumber from 'bignumber.js'; +import { Tezosish } from '../../../src/services/common-interfaces'; +import { patch } from '../../../test/services/patch'; +import { Tezos } from '../../../src/chains/tezos/tezos'; +import { QuipuSwap } from '../../../src/connectors/quipuswap/quipuswap'; + + +describe('QuipuSwap', () => { + let quipuswap: QuipuSwap; + let tezos: Tezosish; + + const patchProvider = () => { + patch(tezos.provider.signer, 'publicKeyHash', () => 'tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV'); + patch(tezos.provider.contract, 'batch', () => { + return { + send: () => { + return { + status: 'applied', + hash: 'hash', + results: [] + } + } + } + }); + }; + + beforeAll(async () => { + tezos = Tezos.getInstance('mainnet'); + quipuswap = QuipuSwap.getInstance('mainnet'); + + await tezos.init(); + await quipuswap.init(); + }); + + describe('gasLimitEstimate', () => { + it('should return the gas limit estimate', () => { + const gasLimitEstimate = quipuswap.gasLimitEstimate; + expect(gasLimitEstimate).toEqual(15000); + }); + }); + + describe('getAllowedSlippage', () => { + it('should return the allowed slippage from the configuration', () => { + const allowedSlippage = quipuswap.getAllowedSlippage(); + expect(allowedSlippage).toEqual(new BigNumber('0.5')); + }); + + it('should return the allowed slippage from the parameter', () => { + const allowedSlippage = quipuswap.getAllowedSlippage('1/20'); + expect(allowedSlippage).toEqual(new BigNumber('5')); + }); + + it('should return the allowed slippage from the configuration if the parameter is invalid', () => { + const allowedSlippage = quipuswap.getAllowedSlippage('invalid'); + expect(allowedSlippage).toEqual(new BigNumber('0.5')); + }); + }); + + describe('estimateSellTrade', () => { + it('should return the expected trade for a valid trade', async () => { + const baseToken = 'QUIPU'; + const quoteToken = 'XTZ'; + const amount = new BigNumber(1); + const expectedTrade = quipuswap.estimateSellTrade(baseToken, quoteToken, amount); + expect(expectedTrade.outputAmount).toBeDefined(); + expect(expectedTrade.trade).toBeDefined(); + }); + }); + + describe('estimateBuyTrade', () => { + it('should return the expected trade for a valid trade', async () => { + const baseToken = 'DOGA'; + const quoteToken = 'XTZ'; + const amount = new BigNumber(1); + const expectedTrade = quipuswap.estimateBuyTrade(baseToken, quoteToken, amount); + expect(expectedTrade.inputAmount).toBeDefined(); + expect(expectedTrade.trade).toBeDefined(); + }); + }); + + describe('executeTrade', () => { + it('should execute the trade and return the hash and operations', async () => { + patchProvider(); + const baseToken = 'CTEZ'; + const quoteToken = 'XTZ'; + const amount = new BigNumber(1); + const expectedTrade = quipuswap.estimateBuyTrade(baseToken, quoteToken, amount); + const executedTrade = await quipuswap.executeTrade(tezos.provider, expectedTrade.trade); + expect(executedTrade.hash).toBeDefined(); + expect(executedTrade.operations).toBeDefined(); + }); + }); +}); \ No newline at end of file diff --git a/test/connectors/pancakeswap/pancakeswap.routes.test.ts b/test/connectors/pancakeswap/pancakeswap.routes.test.ts index 632bb73ee8..9cb341d4a0 100644 --- a/test/connectors/pancakeswap/pancakeswap.routes.test.ts +++ b/test/connectors/pancakeswap/pancakeswap.routes.test.ts @@ -9,10 +9,10 @@ let bsc: BinanceSmartChain; let pancakeswap: PancakeSwap; beforeAll(async () => { - bsc = BinanceSmartChain.getInstance('testnet'); + bsc = BinanceSmartChain.getInstance('mainnet'); patchEVMNonceManager(bsc.nonceManager); await bsc.init(); - pancakeswap = PancakeSwap.getInstance('binance-smart-chain', 'testnet'); + pancakeswap = PancakeSwap.getInstance('binance-smart-chain', 'mainnet'); await pancakeswap.init(); }); @@ -42,17 +42,17 @@ const patchStoredTokenList = () => { patch(bsc, 'tokenList', () => { return [ { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }, { - chainId: 97, + chainId: 56, name: 'DAI', symbol: 'DAI', - address: '0x8a9424745056Eb399FD19a0EC26A14316684e274', + address: '0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3', decimals: 18, }, ]; @@ -63,18 +63,18 @@ const patchGetTokenBySymbol = () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { return { - chainId: 97, + chainId: 56, name: 'DAI', symbol: 'DAI', - address: '0x8a9424745056Eb399FD19a0EC26A14316684e274', + address: '0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3', decimals: 18, }; } @@ -84,10 +84,10 @@ const patchGetTokenBySymbol = () => { const patchGetTokenByAddress = () => { patch(pancakeswap, 'getTokenByAddress', () => { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; }); @@ -156,7 +156,7 @@ describe('POST /amm/price', () => { .post(`/amm/price`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -185,7 +185,7 @@ describe('POST /amm/price', () => { .post(`/amm/price`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -206,10 +206,10 @@ describe('POST /amm/price', () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { @@ -222,7 +222,7 @@ describe('POST /amm/price', () => { .post(`/amm/price`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DOGE', base: 'WBNB', @@ -239,10 +239,10 @@ describe('POST /amm/price', () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { @@ -255,7 +255,7 @@ describe('POST /amm/price', () => { .post(`/amm/price`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'SHIBA', @@ -284,7 +284,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -306,7 +306,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -324,7 +324,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -355,7 +355,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -377,7 +377,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -397,7 +397,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -414,10 +414,10 @@ describe('POST /amm/trade', () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { @@ -429,7 +429,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'WBNB', base: 'BITCOIN', @@ -449,10 +449,10 @@ describe('POST /amm/trade', () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { @@ -464,7 +464,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'BITCOIN', base: 'WBNB', @@ -485,7 +485,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -505,7 +505,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -525,7 +525,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -545,7 +545,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', diff --git a/test/connectors/pancakeswap/pancakeswap.test.ts b/test/connectors/pancakeswap/pancakeswap.test.ts index 61816d954e..342443a1ad 100644 --- a/test/connectors/pancakeswap/pancakeswap.test.ts +++ b/test/connectors/pancakeswap/pancakeswap.test.ts @@ -1,12 +1,8 @@ jest.useFakeTimers(); import { - Fetcher, - Pair, Percent, - Route, Token, - TokenAmount, - Trade, + CurrencyAmount, TradeType, } from '@pancakeswap/sdk'; import { BigNumber } from 'ethers'; @@ -20,23 +16,23 @@ let bsc: BinanceSmartChain; let pancakeswap: PancakeSwap; const WBNB = new Token( - 97, - '0xae13d989dac2f0debff460ac112a837c89baa7cd', + 56, + '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 18, 'WBNB' ); const DAI = new Token( - 97, - '0x8a9424745056Eb399FD19a0EC26A14316684e274', + 56, + '0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3', 18, 'DAI' ); beforeAll(async () => { - bsc = BinanceSmartChain.getInstance('testnet'); + bsc = BinanceSmartChain.getInstance('mainnet'); patchEVMNonceManager(bsc.nonceManager); await bsc.init(); - pancakeswap = PancakeSwap.getInstance('binance-smart-chain', 'testnet'); + pancakeswap = PancakeSwap.getInstance('binance-smart-chain', 'mainnet'); await pancakeswap.init(); }); @@ -48,37 +44,38 @@ afterAll(async () => { await bsc.close(); }); -const patchFetchPairData = () => { - patch(Fetcher, 'fetchPairData', () => { - return new Pair( - new TokenAmount(WBNB, '2000000000000000000'), - new TokenAmount(DAI, '1000000000000000000') - ); - }); -}; - -const patchTrade = (key: string, error?: Error) => { - patch(Trade, key, () => { - if (error) return []; - const WBNB_DAI = new Pair( - new TokenAmount(WBNB, '2000000000000000000'), - new TokenAmount(DAI, '1000000000000000000') - ); - const DAI_TO_WBNB = new Route([WBNB_DAI], DAI); - return [ - new Trade( - DAI_TO_WBNB, - new TokenAmount(DAI, '1000000000000000'), - TradeType.EXACT_INPUT - ), - ]; - }); -}; +// const patchFetchPairData = () => { +// patch(Fetcher, 'fetchPairData', () => { +// return new Pair( +// CurrencyAmount.fromRawAmount(WBNB, '2000000000000000000'), +// CurrencyAmount.fromRawAmount(DAI, '1000000000000000000') +// ); +// }); +// }; + +// const patchTrade = (key: string, error?: Error) => { +// patch(Trade, key, () => { +// if (error) return []; +// const WBNB_DAI = new Pair( +// CurrencyAmount.fromRawAmount(WBNB, '2000000000000000000'), +// CurrencyAmount.fromRawAmount(DAI, '1000000000000000000') +// ); +// const DAI_TO_WBNB = new Route([WBNB_DAI], DAI, WBNB); +// return [ +// new Trade( +// DAI_TO_WBNB, +// CurrencyAmount.fromRawAmount(DAI, '1000000000000000'), +// TradeType.EXACT_INPUT +// ), +// ]; +// }); +// }; describe('verify PancakeSwap estimateSellTrade', () => { it('Should return an ExpectedTrade when available', async () => { - patchFetchPairData(); - patchTrade('bestTradeExactIn'); + patch(pancakeswap, "getBestTrade", async () => { + return { tradeType: TradeType.EXACT_INPUT, inputAmount: CurrencyAmount.fromRawAmount(WBNB, "10000"), outputAmount: CurrencyAmount.fromRawAmount(DAI, "100000"), routes: [] }; + }) const expectedTrade = await pancakeswap.estimateSellTrade( WBNB, @@ -90,8 +87,10 @@ describe('verify PancakeSwap estimateSellTrade', () => { }); it('Should throw an error if no pair is available', async () => { - patchFetchPairData(); - patchTrade('bestTradeExactIn', new Error('error getting trade')); + + patch(pancakeswap, "getBestTrade", async () => { + return null; + }) await expect(async () => { await pancakeswap.estimateSellTrade(WBNB, DAI, BigNumber.from(1)); @@ -101,8 +100,9 @@ describe('verify PancakeSwap estimateSellTrade', () => { describe('verify PancakeSwap estimateBuyTrade', () => { it('Should return an ExpectedTrade when available', async () => { - patchFetchPairData(); - patchTrade('bestTradeExactOut'); + patch(pancakeswap, "getBestTrade", async () => { + return { tradeType: TradeType.EXACT_OUTPUT, inputAmount: CurrencyAmount.fromRawAmount(WBNB, "10000"), outputAmount: CurrencyAmount.fromRawAmount(DAI, "100000"), routes: [] }; + }) const expectedTrade = await pancakeswap.estimateBuyTrade( WBNB, @@ -114,8 +114,9 @@ describe('verify PancakeSwap estimateBuyTrade', () => { }); it('Should return an error if no pair is available', async () => { - patchFetchPairData(); - patchTrade('bestTradeExactOut', new Error('error getting trade')); + patch(pancakeswap, "getBestTrade", async () => { + return null; + }) await expect(async () => { await pancakeswap.estimateBuyTrade(WBNB, DAI, BigNumber.from(1)); diff --git a/yarn.lock b/yarn.lock index 6c5493e1fe..ebf49279e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@adraffy/ens-normalize@1.10.0": version "1.10.0" resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" @@ -1073,6 +1078,18 @@ enabled "2.0.x" kuler "^2.0.0" +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -1088,6 +1105,26 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.56.0": + version "8.56.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" + integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== + "@ethereumjs/common@2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.5.0.tgz#ec61551b31bef7a69d1dc634d8932468866a4268" @@ -1131,7 +1168,7 @@ "@ethersproject-xdc/keccak256" "file:vendor/@ethersproject-xdc/keccak256" "@ethersproject-xdc/logger" "file:vendor/@ethersproject-xdc/logger" "@ethersproject-xdc/properties" "file:vendor/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:vendor/@ethersproject-xdc/strings" + "@ethersproject-xdc/strings" "file:vendor/@ethersproject-xdc/strings" "@ethersproject-xdc/abstract-provider@file:vendor/@ethersproject-xdc/abstract-provider": version "5.7.0" @@ -1875,7 +1912,7 @@ "@graphql-typed-document-node/core" "^3.1.1" tslib "^2.4.0" -"@graphql-typed-document-node/core@^3.1.1": +"@graphql-typed-document-node/core@^3.1.1", "@graphql-typed-document-node/core@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== @@ -1996,6 +2033,15 @@ "@types/bn.js" "^4.11.3" bn.js "^4.11.8" +"@humanwhocodes/config-array@^0.11.13": + version "0.11.13" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" + integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== + dependencies: + "@humanwhocodes/object-schema" "^2.0.1" + debug "^4.1.1" + minimatch "^3.0.5" + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -2005,11 +2051,21 @@ debug "^4.1.1" minimatch "^3.0.4" +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + "@humanwhocodes/object-schema@^1.2.0": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/object-schema@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" + integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== + "@improbable-eng/grpc-web@^0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web/-/grpc-web-0.13.0.tgz#289e6fc4dafc00b1af8e2b93b970e6892299014d" @@ -2597,7 +2653,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -2832,6 +2888,28 @@ resolved "https://registry.yarnpkg.com/@pancakeswap/chains/-/chains-0.3.0.tgz#e509dfd9c8387f76b893e3a0a5b835331752b32c" integrity sha512-a4U8pzfxQsnS0nUpvi8qL2X6l2C/IUTFJcmt3UNAQv2L2CPUSryt1rLarG91O1Bb/UMfv90UCPrXJcHWpaYSMg== +"@pancakeswap/multicall@3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@pancakeswap/multicall/-/multicall-3.3.2.tgz#deaf60cfa0c25b76fbbd0c38333cee5b0110849a" + integrity sha512-H4DOyTYEXs49tgR0QrxkjJmDImxSu5rTJyaRc2kEDluU1kDsFffPiX4nrnewEmmvYFxAfin5jn9sp6D7dV7jEg== + dependencies: + "@pancakeswap/chains" "0.3.0" + "@pancakeswap/sdk" "5.7.3" + viem "1.19.9" + +"@pancakeswap/sdk@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-5.1.0.tgz#ab15c565c081d62cf0d186d18c7fcac114a6b192" + integrity sha512-/vFAR8zzyWT9n0twC0MdnkV7QOr40IAgC48wkbL2S07Anjgkd38dkRfP8X3dYCsY8aGDT+c9cCJmGBdte82Lng== + dependencies: + "@pancakeswap/swap-sdk-core" "1.0.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + viem "^1.2.9" + "@pancakeswap/sdk@5.7.3": version "5.7.3" resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-5.7.3.tgz#973ac77f0ac9920dea4c711f2bea396a88efdeb5" @@ -2846,18 +2924,41 @@ toformat "^2.0.0" viem "1.19.9" -"@pancakeswap/sdk@^2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-2.4.5.tgz#783c02efc7ca89d2297b0b07040639d90c3e610d" - integrity sha512-qfHOPGXitDQ5y1dmYloe6UQe/0Ki0enow4MLtr5W49Jl7ZXr8oB6XV7usYtT63R7vqLSKY83rd8Wyw18vxP7dA== +"@pancakeswap/sdk@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-4.0.0.tgz#6068bf3c1b031ef13f4b223ee3cbeff7ddbb5d96" + integrity sha512-gUQr3svj/gk85ef7Dztu1itK0HqfUYDki8d4WM4SpkIeqebo3r9DFuBJ7G6tlQaefNthQF0Loi1HB+wLP+2o3w== dependencies: + "@pancakeswap/swap-sdk-core" "1.0.0" big.js "^5.2.2" decimal.js-light "^2.5.0" - jsbi "^3.1.4" tiny-invariant "^1.1.0" tiny-warning "^1.0.3" toformat "^2.0.0" +"@pancakeswap/smart-router@^4.2.1": + version "4.11.1" + resolved "https://registry.yarnpkg.com/@pancakeswap/smart-router/-/smart-router-4.11.1.tgz#e5a1055c5427f0e41bc3de454efd1044e0798826" + integrity sha512-9iqyk+5GaHLB3CqXtCYbJ6OpDfROcI2yukojzXKyngTR6IGfpUGh9Tg2Ggh6TOgwDCEqD3dhDc1BooAvkceOIA== + dependencies: + "@pancakeswap/chains" "0.3.0" + "@pancakeswap/multicall" "3.3.2" + "@pancakeswap/sdk" "5.7.3" + "@pancakeswap/swap-sdk-core" "1.0.0" + "@pancakeswap/token-lists" "0.0.9" + "@pancakeswap/tokens" "0.5.6" + "@pancakeswap/v3-sdk" "3.7.2" + async-retry "^1.3.1" + debug "^4.3.4" + graphql "^16.8.1" + graphql-request "5.0.0" + lodash "^4.17.21" + mnemonist "^0.38.3" + stats-lite "^2.2.0" + tiny-invariant "^1.3.0" + viem "1.19.9" + zod "^3.22.3" + "@pancakeswap/swap-sdk-core@1.0.0", "@pancakeswap/swap-sdk-core@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@pancakeswap/swap-sdk-core/-/swap-sdk-core-1.0.0.tgz#30ab333943ecca5484cd10cbc493841fb553728d" @@ -2869,6 +2970,15 @@ tiny-warning "^1.0.3" toformat "^2.0.0" +"@pancakeswap/token-lists@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@pancakeswap/token-lists/-/token-lists-0.0.8.tgz#a168cb8cdc7bf224812cc0e7f0382c46f3b93166" + integrity sha512-KTq31adfPqwqpHBLZ/rRkVlFpVIehB8UcmsuZpY1hjXqdNXUf5lO/Kad4tIxYtIlx9nrWM6R1laYxfISGzAMYQ== + dependencies: + "@pancakeswap/swap-sdk-core" "1.0.0" + ajv "^6.12.3" + lodash "^4.17.21" + "@pancakeswap/token-lists@0.0.9": version "0.0.9" resolved "https://registry.yarnpkg.com/@pancakeswap/token-lists/-/token-lists-0.0.9.tgz#ffa2e7eb4e8d0ef1b8984b22d8b2fb529787693f" @@ -2887,6 +2997,14 @@ "@pancakeswap/sdk" "5.7.3" "@pancakeswap/token-lists" "0.0.9" +"@pancakeswap/tokens@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@pancakeswap/tokens/-/tokens-0.1.6.tgz#c6f59c246bdc9f37322101acc2be700aee9b584b" + integrity sha512-JrHNVyBIxSRD62Prn+yeMbxcZdLs5yFY1Y1Xaex6+kCxDz/WNAKI+/Q9WnJZ7m/jsfuB/vjJN89gqCxGAc/VVw== + dependencies: + "@pancakeswap/sdk" "5.1.0" + "@pancakeswap/token-lists" "0.0.8" + "@pancakeswap/v3-core@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@pancakeswap/v3-core/-/v3-core-1.0.2.tgz#f5abf6a98535f33edebbfc11ab40b4fdcee51420" @@ -2903,7 +3021,7 @@ "@uniswap/v2-core" "1.0.1" base64-sol "1.0.1" -"@pancakeswap/v3-sdk@^3.7.0": +"@pancakeswap/v3-sdk@3.7.2", "@pancakeswap/v3-sdk@^3.7.0": version "3.7.2" resolved "https://registry.yarnpkg.com/@pancakeswap/v3-sdk/-/v3-sdk-3.7.2.tgz#ce475303802ada6c9b7d6781529b0c23033f638c" integrity sha512-8uSMUaDo69zx+PhODH1TiTdPvuaTlLzyyudpODV8Ig6LKWO2owVIRuhXwQHKXE8N3/0j7JN6ejJrM/sbY3kadA== @@ -3476,6 +3594,14 @@ resolved "https://registry.yarnpkg.com/@taquito/core/-/core-17.2.0.tgz#cf1e23ba5548aca36d2fb7263973e17806849536" integrity sha512-Sijn6uhSJUPgk9TzlxOAiJpfF7HogjNepmjGia3Eq9LidJMg6gqL+VRYYtCIAzNQl7Ge8dkqUx9yIibQFHPR7w== +"@taquito/http-utils@^15.0.1", "@taquito/http-utils@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@taquito/http-utils/-/http-utils-15.1.0.tgz#66f3ce220c483e33d6b31bca6e0c76b5b895ed9b" + integrity sha512-Uug5hN0XvMlFFN+rxSMW+Y9Z8pw5uqHRDZC83eLOBSijbpMo+ScG/2nKkC8MUUrqLaLeHru1HD4kT5DHc1fI+A== + dependencies: + "@vespaiach/axios-fetch-adapter" "github:ecadlabs/axios-fetch-adapter" + axios "^0.26.0" + "@taquito/http-utils@^17.2.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@taquito/http-utils/-/http-utils-17.2.0.tgz#99a5c2e52d7553d7c256366b6629440e4507c955" @@ -3484,6 +3610,14 @@ "@taquito/core" "^17.2.0" axios "0.26.0" +"@taquito/local-forging@^15.0.1", "@taquito/local-forging@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@taquito/local-forging/-/local-forging-15.1.0.tgz#11404d4b90d4b1f4f6e3f7aa591e8227bf08e246" + integrity sha512-ib/2RqtxQQC9SjyTB9T5OSc5yUx9GUSdMOA4dmtiiFcN2+AG+aw7ixn6Hjt9Td8ZIOPt9H6HkyTypKrX7+cENw== + dependencies: + "@taquito/utils" "^15.1.0" + bignumber.js "^9.1.0" + "@taquito/local-forging@^17.2.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@taquito/local-forging/-/local-forging-17.2.0.tgz#7a52da459aa87411df1dd2f477d6604eca91ec02" @@ -3493,6 +3627,11 @@ "@taquito/utils" "^17.2.0" bignumber.js "^9.1.0" +"@taquito/michel-codec@^15.0.1", "@taquito/michel-codec@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@taquito/michel-codec/-/michel-codec-15.1.0.tgz#b4452757ff02c40b110ec5ecafad1f659e1de4e3" + integrity sha512-wKucIhs7vhaq5H+YSF2f6Qu9+g+QiEL6MPc5ROpxBrXJTeKSwBOEIpfqcKfkfMuecJyHZJW3glNfkpAVTCgkxg== + "@taquito/michel-codec@^17.2.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@taquito/michel-codec/-/michel-codec-17.2.0.tgz#6a71e50c3043fd31c0cc4b635eb14f38c8270963" @@ -3500,6 +3639,16 @@ dependencies: "@taquito/core" "^17.2.0" +"@taquito/michelson-encoder@^15.0.1", "@taquito/michelson-encoder@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@taquito/michelson-encoder/-/michelson-encoder-15.1.0.tgz#1b3250445d4cc7e945e6a0ed9f8deaf209e62ada" + integrity sha512-uQMEu3g+8WcYb5ZV6+XGvoWJhKoNxU0F2RqodLJB7UxQ1rI/OMa+VlxSLMt4niIxpKXqnO9j4tD7Y4mPC3ufaA== + dependencies: + "@taquito/rpc" "^15.1.0" + "@taquito/utils" "^15.1.0" + bignumber.js "^9.1.0" + fast-json-stable-stringify "^2.1.0" + "@taquito/michelson-encoder@^17.2.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@taquito/michelson-encoder/-/michelson-encoder-17.2.0.tgz#ba32ee0214076a58b6e37fc83d802f1f5e123c58" @@ -3510,6 +3659,15 @@ bignumber.js "^9.1.0" fast-json-stable-stringify "^2.1.0" +"@taquito/rpc@^15.0.1", "@taquito/rpc@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@taquito/rpc/-/rpc-15.1.0.tgz#47f973d1f7d15cb56a4095c7a2a4d1803c5181c7" + integrity sha512-OeQA8QwT+s6IUmLaF5yeWruPYzWi/DVCA3kl+AaQ8IFfCMzmAW/MszbbNkJSzHpY2p4jPBwdRNxg3qeJdL482A== + dependencies: + "@taquito/http-utils" "^15.1.0" + "@taquito/utils" "^15.1.0" + bignumber.js "^9.1.0" + "@taquito/rpc@^17.0.0", "@taquito/rpc@^17.2.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@taquito/rpc/-/rpc-17.2.0.tgz#2ab89aa5d2cbff6986ec212bc01c37c75a938760" @@ -3520,6 +3678,25 @@ "@taquito/utils" "^17.2.0" bignumber.js "^9.1.0" +"@taquito/signer@^15.0.1": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@taquito/signer/-/signer-15.1.0.tgz#0cb7bc9612e7f5ffbefc274f0aa6da27f5d755fd" + integrity sha512-VP7hS8cYQ6cMerVkbD5X3AqpoIXvh72xNuv3++R4reEjdl+E3VWs1CZZGnJj6yzlFV21SrdGKSILx8Rl3Ql4DA== + dependencies: + "@stablelib/blake2b" "^1.0.1" + "@stablelib/ed25519" "^1.0.3" + "@stablelib/hmac" "^1.0.1" + "@stablelib/nacl" "^1.0.4" + "@stablelib/pbkdf2" "^1.0.1" + "@stablelib/sha512" "^1.0.1" + "@taquito/taquito" "^15.1.0" + "@taquito/utils" "^15.1.0" + "@types/bn.js" "^5.1.1" + bip39 "^3.0.4" + elliptic "^6.5.4" + pbkdf2 "^3.1.2" + typedarray-to-buffer "^4.0.0" + "@taquito/signer@^17.0.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@taquito/signer/-/signer-17.2.0.tgz#4173d4fab3b4a7599aa7be5f3934b691dff73e67" @@ -3539,6 +3716,34 @@ pbkdf2 "^3.1.2" typedarray-to-buffer "^4.0.0" +"@taquito/taquito@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@taquito/taquito/-/taquito-15.0.1.tgz#da8d60e22c06de182bc745e735d7573604034dbd" + integrity sha512-DA0ikg/mSubmdQc2rCNoZkyTpwjoXsddTlptphkAGtqDGEb+VSO3fcQWa/T+NHupWraXXd+pqdOEewbR1cYhvw== + dependencies: + "@taquito/http-utils" "^15.0.1" + "@taquito/local-forging" "^15.0.1" + "@taquito/michel-codec" "^15.0.1" + "@taquito/michelson-encoder" "^15.0.1" + "@taquito/rpc" "^15.0.1" + "@taquito/utils" "^15.0.1" + bignumber.js "^9.1.0" + rxjs "^6.6.3" + +"@taquito/taquito@^15.0.1", "@taquito/taquito@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@taquito/taquito/-/taquito-15.1.0.tgz#9a3340a8bcaa8bd6e9567776cea1c1659aafe5e9" + integrity sha512-2AXWeNoXsmMOSkJVXtXjOlJkS+hKXITaSybMA6nJuS1YWY4e7iAr678Y6UgVEHRJxeGohX4R4Ww12Ymr3Sfedg== + dependencies: + "@taquito/http-utils" "^15.1.0" + "@taquito/local-forging" "^15.1.0" + "@taquito/michel-codec" "^15.1.0" + "@taquito/michelson-encoder" "^15.1.0" + "@taquito/rpc" "^15.1.0" + "@taquito/utils" "^15.1.0" + bignumber.js "^9.1.0" + rxjs "^6.6.3" + "@taquito/taquito@^17.0.0", "@taquito/taquito@^17.2.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@taquito/taquito/-/taquito-17.2.0.tgz#cbf896340ed8ced8defbb01d98c286c166eea062" @@ -3554,6 +3759,21 @@ bignumber.js "^9.1.0" rxjs "^7.8.1" +"@taquito/utils@^15.0.1", "@taquito/utils@^15.1.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@taquito/utils/-/utils-15.1.0.tgz#c72f07c4fe369920620809a23808817db4b7a221" + integrity sha512-lqVThoFMmOKPg9jyREr4A63cpeckf5esCwOyOAW3sm+yCxD9s5khnBPtH8s52cRVnChFdwk/eqmADka9gat5hw== + dependencies: + "@stablelib/blake2b" "^1.0.1" + "@stablelib/ed25519" "^1.0.3" + "@types/bs58check" "^2.1.0" + bignumber.js "^9.1.0" + blakejs "^1.2.1" + bs58check "^2.1.2" + buffer "^6.0.3" + elliptic "^6.5.4" + typedarray-to-buffer "^4.0.0" + "@taquito/utils@^17.2.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@taquito/utils/-/utils-17.2.0.tgz#fca1a54fb0321bd1c99ecfe2a33b117a97df86c0" @@ -4387,6 +4607,11 @@ "@typescript-eslint/types" "4.33.0" eslint-visitor-keys "^2.0.0" +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@uniswap/default-token-list@^2.0.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-2.3.0.tgz#e5e522e775791999643aac9b0faf1ccfb4c49bd8" @@ -4574,6 +4799,10 @@ "@uniswap/v3-core" "1.0.0" "@uniswap/v3-periphery" "^1.0.1" +"@vespaiach/axios-fetch-adapter@github:ecadlabs/axios-fetch-adapter": + version "0.3.1" + resolved "https://codeload.github.com/ecadlabs/axios-fetch-adapter/tar.gz/167684f522e90343b9f3439d9a43ac571e2396f6" + "@wagmi/chains@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@wagmi/chains/-/chains-1.0.0.tgz#41710941f2c2a699a246c4e3a6112b4efd996171" @@ -4705,7 +4934,7 @@ accepts@^1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -4730,6 +4959,11 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + adm-zip@^0.4.16: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" @@ -5152,7 +5386,7 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async-retry@^1.2.1, async-retry@^1.3.1: +async-retry@1.3.3, async-retry@^1.2.1, async-retry@^1.3.1: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== @@ -5234,6 +5468,13 @@ axios@^0.21.1, axios@^0.21.2: dependencies: follow-redirects "^1.14.0" +axios@^0.26.0: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + axios@^0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" @@ -5482,7 +5723,7 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -blakejs@^1.1.0, blakejs@^1.2.1: +blakejs@^1.1.0, blakejs@^1.1.1, blakejs@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== @@ -5963,6 +6204,11 @@ cbor@^5.2.0: bignumber.js "^9.0.1" nofilter "^1.0.4" +chai-bignumber@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/chai-bignumber/-/chai-bignumber-3.1.0.tgz#e196456c760df21f0e124f6df922289ea15a7e4c" + integrity sha512-omxEc80jAU+pZwRmoWr3aEzeLad4JW3iBhLRQlgISvghBdIxrMT7mVAGsDz4WSyCkKowENshH2j9OABAhld7QQ== + chain-registry@^1.15.0: version "1.19.0" resolved "https://registry.yarnpkg.com/chain-registry/-/chain-registry-1.19.0.tgz#6d2d56b56efebee0d15ced8f9bd8329a099ce04d" @@ -6017,6 +6263,11 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +child_process@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/child_process/-/child_process-1.0.2.tgz#b1f7e7fc73d25e7fd1d455adc94e143830182b5a" + integrity sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g== + chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -6574,7 +6825,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -6881,6 +7132,11 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dotenv@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotenv@^14.2.0: version "14.3.2" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.3.2.tgz#7c30b3a5f777c79a3429cb2db358eef6751e8369" @@ -7298,6 +7554,14 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" @@ -7322,6 +7586,11 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + eslint@^7.25.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -7368,6 +7637,50 @@ eslint@^7.25.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +eslint@^8.17.0: + version "8.56.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" + integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.56.0" + "@humanwhocodes/config-array" "^0.11.13" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -7377,12 +7690,21 @@ espree@^7.3.0, espree@^7.3.1: acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: +esquery@^1.4.0, esquery@^1.4.2: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== @@ -8019,7 +8341,7 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-up@5.0.0: +find-up@5.0.0, find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== @@ -8389,6 +8711,13 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" @@ -8454,6 +8783,13 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + globals@^13.6.0, globals@^13.9.0: version "13.20.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" @@ -8577,6 +8913,21 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +graphql-request@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-5.0.0.tgz#7504a807d0e11be11a3c448e900f0cc316aa18ef" + integrity sha512-SpVEnIo2J5k2+Zf76cUkdvIRaq5FMZvGQYnA4lUWYbc99m+fHh4CZYRRO/Ff4tCLQ613fzCm3SiDT64ubW5Gyw== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + cross-fetch "^3.1.5" + extract-files "^9.0.0" + form-data "^3.0.0" + graphql-request@^3.4.0: version "3.7.0" resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.7.0.tgz#c7406e537084f8b9788541e3e6704340ca13055b" @@ -8586,6 +8937,14 @@ graphql-request@^3.4.0: extract-files "^9.0.0" form-data "^3.0.0" +graphql-request@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.1.0.tgz#f4eb2107967af3c7a5907eb3131c671eac89be4f" + integrity sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw== + dependencies: + "@graphql-typed-document-node/core" "^3.2.0" + cross-fetch "^3.1.5" + graphql-tag@^2.11.0, graphql-tag@^2.12.6: version "2.12.6" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" @@ -8603,6 +8962,11 @@ graphql@^16.3.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== +graphql@^16.8.1: + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== + growl@1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" @@ -9322,6 +9686,11 @@ is-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -10928,7 +11297,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -"minimatch@2 || 3", minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +"minimatch@2 || 3", minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -11647,6 +12016,18 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + original-require@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/original-require/-/original-require-1.0.1.tgz#0f130471584cd33511c5ec38c8d59213f9ac5e20" @@ -12184,6 +12565,11 @@ prettier@^2.3.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.5.tgz#3dd8ae1ebddc4f6aa419c9b64d8c8319a7e0d982" integrity sha512-3gzuxrHbKUePRBB4ZeU08VNkUcqEHaUaouNt0m7LGP4Hti/NuB07C7PPTM/LkWqXoJYJn2McEo5+kxPNrtQkLQ== +prettier@^2.7.0: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + pretty-format@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f" @@ -12412,6 +12798,26 @@ quickswap-sdk@^3.0.8: tiny-warning "^1.0.3" toformat "^2.0.0" +quipuswap-v3-sdk@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/quipuswap-v3-sdk/-/quipuswap-v3-sdk-0.0.7.tgz#7b86a38eb7bf0f4c7e73ce68c637bac4eb07e37b" + integrity sha512-zkye1ykEGJ3TA2P1nmNQQ9cv4R4A4hK+3dfPJMk9AGp/GQuO30R2JxUobacDo4HvSTlmx6Xk3srEOTnajbwUTg== + dependencies: + "@taquito/http-utils" "^15.0.1" + "@taquito/local-forging" "^15.0.1" + "@taquito/michel-codec" "^15.0.1" + "@taquito/michelson-encoder" "^15.0.1" + "@taquito/rpc" "^15.0.1" + "@taquito/signer" "^15.0.1" + "@taquito/taquito" "^15.0.1" + blakejs "^1.1.1" + chai-bignumber "^3.0.0" + child_process "^1.0.2" + dotenv "10.0.0" + ts-node "^10.2.1" + typescript "^4.4.3" + yargs "^17.2.1" + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -12871,6 +13277,13 @@ rustbn.js@~0.2.0: resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== +rxjs@^6.6.3: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + rxjs@^7.4.0, rxjs@^7.8.0: version "7.8.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" @@ -13739,6 +14152,17 @@ swap-case@^1.1.0: lower-case "^1.1.1" upper-case "^1.1.1" +swap-router-sdk@^1.21.1: + version "1.21.1" + resolved "https://registry.yarnpkg.com/swap-router-sdk/-/swap-router-sdk-1.21.1.tgz#e6febfd5564ce3482518383e938b200908daf140" + integrity sha512-Nz5m5074D+lmflF972NVnWvQOoBsdE6C9HRs0X6ZkXWtgfQiT8IfqMz03XPQeLmsL6OupCKmpLBj2YSmcV1APQ== + dependencies: + "@taquito/taquito" "15.0.1" + async-retry "1.3.3" + bignumber.js "^9.1.0" + eslint "^8.17.0" + prettier "^2.7.0" + swarm-js@0.1.39: version "0.1.39" resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.39.tgz#79becb07f291d4b2a178c50fee7aa6e10342c0e8" @@ -14099,6 +14523,25 @@ ts-node@^10.0.0: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +ts-node@^10.2.1: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tsconfig-paths@^3.14.1: version "3.14.2" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" @@ -14109,7 +14552,7 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1, tslib@^1.9.3: +tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -14263,6 +14706,11 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" +typescript@^4.4.3: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + typescript@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" @@ -14585,6 +15033,20 @@ viem@^0.3.x: isomorphic-ws "5.0.0" ws "8.12.0" +viem@^1.2.9: + version "1.19.15" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.19.15.tgz#0f2307632fa0ef10dfab2d8fdd71fbb842a0a4f5" + integrity sha512-rc87AkyrUUsoOAgMNYP+X/wN4GYwbhP87DkmsqQCYKxxQyzTX0+yliKs6Bxljbjr8ybU72GOb12Oyus6393AjQ== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "0.9.8" + isows "1.0.3" + ws "8.13.0" + vlq@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/vlq/-/vlq-2.0.4.tgz#6057b85729245b9829e3cc7755f95b228d4fe041" @@ -15473,7 +15935,7 @@ yargs@^12.0.2: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" -yargs@^17.3.1: +yargs@^17.2.1, yargs@^17.3.1: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== @@ -15520,3 +15982,8 @@ zen-observable@0.8.15: version "0.8.15" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== + +zod@^3.22.3: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==