UNPKG

ndtr-dexter

Version:

Customizable Typescript SDK for interacting with Cardano DEXs

210 lines (209 loc) 8.53 kB
import { Asset } from '../dex/models/asset'; import { tokensMatch } from "../utils"; export class FetchRequest { constructor(dexter) { this._onDexs = []; this._filteredTokens = []; this._filteredPairs = []; this._dexter = dexter; this._onDexs = Object.values(dexter.availableDexs); } /** * Set the DEX(s) Dexter will fetch data on. */ onDexs(dexs) { this._onDexs = []; (Array.isArray(dexs) ? dexs : [dexs]).forEach((dexName) => { if (!Object.keys(this._dexter.availableDexs).includes(dexName)) { throw new Error(`DEX ${dexName} is not available.`); } this._onDexs = this._onDexs.filter((dex) => { return dex.name !== dexName; }).concat(this._dexter.availableDexs[dexName]); }); return this; } /** * Fetch data on all available DEXs. */ onAllDexs() { this._onDexs = Object.values(this._dexter.availableDexs); return this; } /** * Only fetch pools containing these tokens. */ forTokens(tokens) { this._filteredTokens = tokens; return this; } /** * Only fetch pools containing these token pairs. */ forTokenPairs(tokenPairs) { tokenPairs.forEach((pair) => { if (pair.length !== 2) { throw new Error('Token pair must contain two tokens.'); } if (tokensMatch(pair[0], pair[1])) { throw new Error('Provided pair contains the same tokens. Ensure each pair has differing tokens.'); } }); this._filteredPairs = tokenPairs; return this; } /** * Fetch latest state for a liquidity pool. */ getLiquidityPoolState(liquidityPool) { const dexInstance = this._dexter.dexByName(liquidityPool.dex); if (!dexInstance) { return Promise.reject('Unable to determine DEX from the provided liquidity pool.'); } let liquidityPoolPromises; if (this._dexter.dataProvider) { if (!liquidityPool.address) { return Promise.reject('Liquidity pool must have a set address.'); } const filterableAsset = liquidityPool.assetA === 'lovelace' ? liquidityPool.assetB : liquidityPool.assetA; liquidityPoolPromises = this._dexter.dataProvider.utxos(liquidityPool.address, filterableAsset) .then(async (utxos) => { return await Promise.all(utxos.map(async (utxo) => { return await dexInstance.liquidityPoolFromUtxo(this._dexter.dataProvider, utxo); })).then((liquidityPools) => { return liquidityPools.filter((liquidityPool) => { return liquidityPool !== undefined; }); }); }); } else { liquidityPoolPromises = dexInstance.api.liquidityPools(liquidityPool.assetA, liquidityPool.assetB); } return liquidityPoolPromises .then(async (liquidityPools) => { const possiblePools = liquidityPools.filter((pool) => { return pool !== undefined && pool.uuid === liquidityPool.uuid; }); if (possiblePools.length > 1) { return Promise.reject('Encountered more than 1 possible pool state.'); } if (this._dexter.config.shouldFetchMetadata) { await this.fetchAssetMetadata(possiblePools); } return possiblePools[0]; }); } /** * Fetch all liquidity pools matching token filters. */ getLiquidityPools() { const liquidityPoolPromises = this._onDexs.map((dex) => { if (!this._dexter.dataProvider) { return this.fetchPoolsFromApi(dex); } return dex.liquidityPools(this._dexter.dataProvider) .catch(() => { // Attempt fallback to API return this._dexter.config.shouldFallbackToApi ? this.fetchPoolsFromApi(dex) : []; }); }); return Promise.all(liquidityPoolPromises).then(async (mappedLiquidityPools) => { const liquidityPools = mappedLiquidityPools .flat() .filter((pool) => this.poolMatchesFilter(pool)); if (this._dexter.config.shouldFetchMetadata) { await this.fetchAssetMetadata(liquidityPools); } return liquidityPools; }); } /** * Fetch historic states for a liquidity pool. */ async getLiquidityPoolHistory(liquidityPool) { if (!this._dexter.dataProvider) { return []; // todo } const transactions = await this._dexter.dataProvider.assetTransactions(liquidityPool.lpToken); const liquidityPoolPromises = transactions.map(async (transaction) => { const utxos = await this._dexter.dataProvider .transactionUtxos(transaction.hash); const relevantUtxo = utxos.find((utxo) => { return utxo.address === liquidityPool.address; }); if (!relevantUtxo) { return undefined; } return await this._dexter.availableDexs[liquidityPool.dex].liquidityPoolFromUtxo(this._dexter.dataProvider, relevantUtxo); }); return await Promise.all(liquidityPoolPromises) .then((liquidityPools) => { return liquidityPools.filter((pool) => { return pool !== undefined; }); }); } /** * Fetch asset metadata for the assets in the provided liquidity pools. */ async fetchAssetMetadata(liquidityPools) { const assets = liquidityPools.reduce((results, liquidityPool) => { if (liquidityPool.assetA !== 'lovelace' && !results.some((asset) => asset.id() === liquidityPool.assetA.id())) { results.push(liquidityPool.assetA); } if (liquidityPool.assetB !== 'lovelace' && !results.some((asset) => asset.id() === liquidityPool.assetB.id())) { results.push(liquidityPool.assetB); } return results; }, []); await this._dexter.metadataProvider.fetch(assets) .then((response) => { liquidityPools.forEach((liquidityPool) => { [liquidityPool.assetA, liquidityPool.assetB].forEach((asset) => { if (!(asset instanceof Asset)) { return; } const responseAsset = response.find((metadata) => { return (metadata.policyId === asset.policyId) && (metadata.nameHex === asset.nameHex); }); asset.decimals = responseAsset ? responseAsset.decimals : 0; }); }); }); } /** * Check if a pools assets match the supplied token filters. */ poolMatchesFilter(liquidityPool) { if (!this._filteredTokens.length && !this._filteredPairs.length) { return true; } const inFilteredTokens = this._filteredTokens.some((filterToken) => { return tokensMatch(filterToken, liquidityPool.assetA) || tokensMatch(filterToken, liquidityPool.assetB); }); const inFilteredPairs = this._filteredPairs.some((filterPair) => { return (tokensMatch(filterPair[0], liquidityPool.assetA) && tokensMatch(filterPair[1], liquidityPool.assetB)) || (tokensMatch(filterPair[0], liquidityPool.assetB) && tokensMatch(filterPair[1], liquidityPool.assetA)); }); return inFilteredTokens || inFilteredPairs; } /** * Fetch liquidity pools from DEX APIs using the provided token filters. */ fetchPoolsFromApi(dex) { const filterTokenPromises = this._filteredTokens.map((token) => { return dex.api.liquidityPools(token) .catch(() => []); }); const filterPairPromises = this._filteredPairs.map((pair) => { return dex.api.liquidityPools(pair[0], pair[1]) .catch(() => []); }); return Promise.all(filterTokenPromises.concat(filterPairPromises).flat()).then((allLiquidityPools) => allLiquidityPools.flat()); } }