@talismn/token-rates 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -114,8 +114,8 @@ type FetchDTaoTokenRatesOptions = {
|
|
|
114
114
|
previousRates: TokenRatesList;
|
|
115
115
|
/** custom fetch for the tao-data api (eg with auth headers), defaults to global fetch */
|
|
116
116
|
customFetch?: typeof fetch;
|
|
117
|
-
/** tao-data api url, defaults to https://tda.talisman.xyz */
|
|
118
|
-
taoDataApiUrl?: string;
|
|
117
|
+
/** tao-data api url, defaults to https://tda.talisman.xyz — pass null to skip the 24h-change fetch (eg testnets, which the api has no data for) */
|
|
118
|
+
taoDataApiUrl?: string | null;
|
|
119
119
|
/** per-request timeout for the chain call and the tao-data api call, defaults to 10s */
|
|
120
120
|
timeoutMs?: number;
|
|
121
121
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -114,8 +114,8 @@ type FetchDTaoTokenRatesOptions = {
|
|
|
114
114
|
previousRates: TokenRatesList;
|
|
115
115
|
/** custom fetch for the tao-data api (eg with auth headers), defaults to global fetch */
|
|
116
116
|
customFetch?: typeof fetch;
|
|
117
|
-
/** tao-data api url, defaults to https://tda.talisman.xyz */
|
|
118
|
-
taoDataApiUrl?: string;
|
|
117
|
+
/** tao-data api url, defaults to https://tda.talisman.xyz — pass null to skip the 24h-change fetch (eg testnets, which the api has no data for) */
|
|
118
|
+
taoDataApiUrl?: string | null;
|
|
119
119
|
/** per-request timeout for the chain call and the tao-data api call, defaults to 10s */
|
|
120
120
|
timeoutMs?: number;
|
|
121
121
|
};
|
package/dist/index.js
CHANGED
|
@@ -237,7 +237,7 @@ const fetchDTaoTokenRates = async ({ connector, networkId = DEFAULT_BITTENSOR_NE
|
|
|
237
237
|
const [prices, changes] = await Promise.all([Promise.race([fetchScaledAlphaPricesByNetuid(connector, networkId), (0, _talismn_util.throwAfter)(timeoutMs, `Timed out after ${timeoutMs}ms`)]).catch((err) => {
|
|
238
238
|
log_default.warn("Failed to fetch alpha prices, keeping previous dtao rates", err);
|
|
239
239
|
return null;
|
|
240
|
-
}), getAlphaPriceChangesByNetuid(taoDataApiUrl, customFetch, timeoutMs)]);
|
|
240
|
+
}), taoDataApiUrl === null ? null : getAlphaPriceChangesByNetuid(taoDataApiUrl, customFetch, timeoutMs)]);
|
|
241
241
|
if (!prices) return Object.fromEntries(dtaoTokens.filter((token) => previousRates[token.id]).map((token) => [token.id, previousRates[token.id]]));
|
|
242
242
|
return Object.fromEntries(dtaoTokens.map((token) => {
|
|
243
243
|
const price = prices.get(token.netuid);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["packageJson.name","u16","u64"],"sources":["../package.json","../src/log.ts","../src/types.ts","../src/getDTaoTokenRates.ts","../src/fetchDTaoTokenRates.ts","../src/TalismanTokenRatesDatabase.ts","../src/TokenRates.ts"],"sourcesContent":["","import anylogger from \"anylogger\"\n\nimport packageJson from \"../package.json\"\n\nexport default anylogger(packageJson.name)\n","import type { TokenId } from \"@talismn/chaindata-provider\"\n\nexport const SUPPORTED_CURRENCIES = {\n btc: { name: \"Bitcoin\", symbol: \"₿\" },\n eth: { name: \"Ethereum\", symbol: \"Ξ\" },\n dot: { name: \"Polkadot\", symbol: \"D\" },\n tao: { name: \"Bittensor\", symbol: \"τ\" },\n\n usd: { name: \"US Dollar\", symbol: \"$\" },\n cny: { name: \"Chinese Yuan\", symbol: \"¥\" },\n eur: { name: \"Euro\", symbol: \"€\" },\n gbp: { name: \"British Pound\", symbol: \"£\" },\n cad: { name: \"Canadian Dollar\", symbol: \"C$\" },\n aud: { name: \"Australian Dollar\", symbol: \"A$\" },\n nzd: { name: \"New Zealand Dollar\", symbol: \"NZ$\" },\n jpy: { name: \"Japanese Yen\", symbol: \"¥\" },\n rub: { name: \"Russian Ruble\", symbol: \"₽\" },\n krw: { name: \"South Korean Won\", symbol: \"₩\" },\n idr: { name: \"Indonesian Rupiah\", symbol: \"Rp\" },\n php: { name: \"Philippine Peso\", symbol: \"₱\" },\n thb: { name: \"Thai Baht\", symbol: \"฿\" },\n vnd: { name: \"Vietnamese Dong\", symbol: \"₫\" },\n inr: { name: \"Indian Rupee\", symbol: \"₹\" },\n try: { name: \"Turkish Lira\", symbol: \"₺\" },\n // hkd: { name: \"Hong Kong Dollar\", symbol: \"HK$\" },\n sgd: { name: \"Singapore Dollar\", symbol: \"S$\" },\n // twd: { name: \"Taiwanese Dollar\", symbol: \"NT$\" },\n} as const\n\nexport type TokenRatesStorage = { tokenRates: TokenRatesList }\n\nexport type TokenRatesList = Record<TokenId, TokenRates>\nexport type TokenRates = Record<TokenRateCurrency, TokenRateData | null>\n\nexport type TokenRateData = { price: number; marketCap?: number; change24h?: number }\nexport type TokenRateCurrency = keyof typeof SUPPORTED_CURRENCIES\n\nexport const newTokenRates = (): TokenRates => ({\n btc: null,\n eth: null,\n dot: null,\n tao: null,\n\n usd: null,\n cny: null,\n eur: null,\n gbp: null,\n cad: null,\n aud: null,\n nzd: null,\n jpy: null,\n rub: null,\n krw: null,\n idr: null,\n php: null,\n thb: null,\n vnd: null,\n inr: null,\n try: null,\n // hkd: null,\n sgd: null,\n // twd: null,\n})\n","import { type SubDTaoToken, subNativeTokenId } from \"@talismn/chaindata-provider\"\n\nimport log from \"./log\"\nimport {\n newTokenRates,\n type TokenRateCurrency,\n type TokenRateData,\n type TokenRatesList,\n} from \"./types\"\n\n/** TAO-per-alpha pool price fixed-point scale: 10^9 (TAO decimals) */\nconst SCALED_ALPHA_PRICE_SCALE = 10 ** 9\n\n/**\n * 24h change of an alpha token in a given currency, in percent: the pool price change (alpha vs\n * TAO) compounded with the TAO change in that currency. Unknown without the pool change.\n */\nconst getAlphaChange24h = (\n currency: TokenRateCurrency,\n taoChange24h: number | null | undefined,\n alphaPriceChange24h: number | null | undefined\n): number | undefined => {\n if (typeof alphaPriceChange24h !== \"number\" || !Number.isFinite(alphaPriceChange24h))\n return undefined\n\n // TAO itself doesn't move against the tao currency: the alpha change is the pool change\n if (currency === \"tao\") return alphaPriceChange24h\n\n if (typeof taoChange24h !== \"number\" || !Number.isFinite(taoChange24h)) return undefined\n return ((1 + alphaPriceChange24h / 100) * (1 + taoChange24h / 100) - 1) * 100\n}\n\n/**\n * Rates of a bittensor dtao (subnet alpha) token, derived from its network's native TAO rates\n * scaled by the subnet pool price. To be used for tokens that don't have a coingecko id.\n *\n * @param token\n * @param tokenRates must contain the rates of the network's native (TAO) token\n * @param scaledAlphaPrice current TAO-per-alpha pool price, fixed-point scaled by 10^9 (rao)\n * @param alphaPriceChange24h 24h change of the pool price (alpha vs TAO) in percent, when known\n */\nexport const getDTaoTokenRates = (\n token: SubDTaoToken,\n tokenRates: TokenRatesList,\n scaledAlphaPrice: string | bigint,\n alphaPriceChange24h?: number | null\n) => {\n try {\n const taoTokenId = subNativeTokenId(token.networkId)\n const taoTokenRates = tokenRates[taoTokenId]\n if (!taoTokenRates) return null\n\n // for root subnet, same rates as TAO\n if (token.netuid === 0) return structuredClone(taoTokenRates)\n\n const priceRatio = Number(BigInt(scaledAlphaPrice)) / SCALED_ALPHA_PRICE_SCALE\n\n const alphaRates = newTokenRates()\n for (const [currency, taoRate] of Object.entries(taoTokenRates) as [\n TokenRateCurrency,\n TokenRateData | null,\n ][]) {\n if (!taoRate) {\n alphaRates[currency] = null\n } else {\n alphaRates[currency] = {\n price: taoRate.price * priceRatio,\n marketCap: taoRate.marketCap ? taoRate.marketCap * priceRatio : undefined,\n change24h: getAlphaChange24h(currency, taoRate.change24h, alphaPriceChange24h),\n }\n }\n }\n\n return alphaRates\n } catch (err) {\n log.error(\"Failed to compute dtao token rates\", err)\n return null\n }\n}\n","import type { IChainConnectorDot } from \"@talismn/chain-connectors\"\nimport type { NetworkId, SubDTaoToken, TokenList } from \"@talismn/chaindata-provider\"\nimport { subNativeTokenId } from \"@talismn/chaindata-provider\"\nimport { throwAfter } from \"@talismn/util\"\nimport { Struct, u16, u64, Vector } from \"scale-ts\"\n\nimport { getDTaoTokenRates } from \"./getDTaoTokenRates\"\nimport log from \"./log\"\nimport type { TokenRatesList } from \"./types\"\n\nconst DEFAULT_BITTENSOR_NETWORK_ID = \"bittensor\"\nconst DEFAULT_TAO_DATA_API_URL = \"https://tda.talisman.xyz\"\nconst DEFAULT_TIMEOUT_MS = 10_000\n\n// decoded shape of the SwapRuntimeApi_current_alpha_price_all result\nconst alphaPricesCodec = Vector(Struct({ netuid: u16, price: u64 }))\n\n/** Current TAO-per-alpha pool price of every subnet, keyed by netuid, in the scaledAlphaPrice fixed-point format (rao) */\nconst fetchScaledAlphaPricesByNetuid = async (\n connector: IChainConnectorDot,\n networkId: NetworkId\n): Promise<Map<number, bigint>> => {\n const result = await connector.send<string>(networkId, \"state_call\", [\n \"SwapRuntimeApi_current_alpha_price_all\",\n \"0x\",\n ])\n return new Map(alphaPricesCodec.dec(result).map(({ netuid, price }) => [netuid, price]))\n}\n\nconst ALPHA_PRICE_CHANGES_TTL = 5 * 60_000\nconst alphaPriceChangesCache = new Map<\n string,\n { fetchedAt: number; changes: Map<number, number> }\n>()\n\n/**\n * 24h percent change of every subnet pool price (alpha vs TAO) from the tao-data api — the\n * chain knows the current pool prices but not yesterday's. 5-min in-memory cache; failure is\n * non-fatal (stale cache when available, else no change24h on the computed rates).\n */\nconst getAlphaPriceChangesByNetuid = async (\n apiUrl: string,\n customFetch: typeof fetch,\n timeoutMs: number\n): Promise<Map<number, number> | null> => {\n const cached = alphaPriceChangesCache.get(apiUrl)\n if (cached && performance.now() - cached.fetchedAt < ALPHA_PRICE_CHANGES_TTL)\n return cached.changes\n\n try {\n const response = await customFetch(`${apiUrl}/pools`, {\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!response.ok) throw new Error(`${response.status} ${response.statusText}`)\n const pools = (await response.json()) as {\n netuid: number\n price_change_1_day: number | string\n }[]\n const changes = new Map(\n pools\n .map((pool): [number, number] => [pool.netuid, Number(pool.price_change_1_day)])\n .filter(([, change]) => Number.isFinite(change))\n )\n alphaPriceChangesCache.set(apiUrl, { fetchedAt: performance.now(), changes })\n return changes\n } catch (err) {\n log.warn(\"Failed to fetch alpha price 24h changes\", err)\n return cached?.changes ?? null\n }\n}\n\nexport type FetchDTaoTokenRatesOptions = {\n /** substrate chain connector of the host (only its `send` method is used, for one state_call per invocation) */\n connector: IChainConnectorDot\n /** the bittensor network id, defaults to \"bittensor\" (mainnet) */\n networkId?: NetworkId\n /** token list to price — non-dtao tokens (and dtao tokens with a coingeckoId) are ignored */\n tokens: TokenList\n /** freshly fetched rates list — dtao rates derive from the network's native TAO token rates in it */\n tokenRates: TokenRatesList\n /** previous rates list, used as keep-last fallback when fetching fails */\n previousRates: TokenRatesList\n /** custom fetch for the tao-data api (eg with auth headers), defaults to global fetch */\n customFetch?: typeof fetch\n /** tao-data api url, defaults to https://tda.talisman.xyz */\n taoDataApiUrl?: string\n /** per-request timeout for the chain call and the tao-data api call, defaults to 10s */\n timeoutMs?: number\n}\n\n/**\n * Rates entries for the bittensor dtao (subnet alpha) tokens of `tokens`, priced from their\n * subnet pool combined with the TAO rates in `tokenRates`. On chain-fetch failure, falls\n * back to the token's entry in `previousRates` (keep-last). Never throws — a dtao pricing\n * failure must not discard the rates list it gets merged into.\n *\n * Hosts remain responsible for WHEN to call this (network enabled state, refresh cadence)\n * and for merging the result into their rates list/store.\n */\nexport const fetchDTaoTokenRates = async ({\n connector,\n networkId = DEFAULT_BITTENSOR_NETWORK_ID,\n tokens,\n tokenRates,\n previousRates,\n customFetch = fetch,\n taoDataApiUrl = DEFAULT_TAO_DATA_API_URL,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n}: FetchDTaoTokenRatesOptions): Promise<TokenRatesList> => {\n const dtaoTokens = Object.values(tokens).filter(\n (token): token is SubDTaoToken =>\n token.type === \"substrate-dtao\" && token.networkId === networkId && !token.coingeckoId\n )\n if (!dtaoTokens.length) return {}\n\n // alpha rates derive from the network's native TAO rates: without them there is nothing to\n // price against, and keep-last must not apply (stale entries would display as current)\n if (!tokenRates[subNativeTokenId(networkId)]) return {}\n\n // the two fetches are independent: run them concurrently\n const [prices, changes] = await Promise.all([\n Promise.race([\n fetchScaledAlphaPricesByNetuid(connector, networkId),\n throwAfter(timeoutMs, `Timed out after ${timeoutMs}ms`),\n ]).catch((err) => {\n log.warn(\"Failed to fetch alpha prices, keeping previous dtao rates\", err)\n return null\n }),\n getAlphaPriceChangesByNetuid(taoDataApiUrl, customFetch, timeoutMs),\n ])\n\n if (!prices)\n return Object.fromEntries(\n dtaoTokens\n .filter((token) => previousRates[token.id])\n .map((token) => [token.id, previousRates[token.id]])\n )\n\n return Object.fromEntries(\n dtaoTokens\n .map((token) => {\n // never fabricate a zero price: a missing or zero pool price keeps the last rates\n const price = prices.get(token.netuid)\n const rates = price\n ? getDTaoTokenRates(token, tokenRates, price, changes?.get(token.netuid))\n : null\n return [token.id, rates ?? previousRates[token.id]] as const\n })\n .filter(([, rates]) => !!rates)\n )\n}\n","export const tryToDeleteOldTokenRatesDb = () => {\n try {\n // try and delete it if it's still there\n indexedDB.deleteDatabase(\"TalismanTokenRates\")\n } catch {\n // dont care if it fails\n }\n}\n","import { evmErc20TokenId, type Token, type TokenId } from \"@talismn/chaindata-provider\"\n\nimport {\n SUPPORTED_CURRENCIES,\n type TokenRateCurrency,\n type TokenRateData,\n type TokenRates,\n type TokenRatesList,\n} from \"./types\"\n\nexport class TokenRatesError extends Error {\n response?: Response\n constructor(message: string, response?: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport const ALL_CURRENCY_IDS = Object.keys(SUPPORTED_CURRENCIES) as TokenRateCurrency[]\nexport type CoinsApiConfig = {\n apiUrl: string\n}\n\nexport const DEFAULT_COINSAPI_CONFIG: CoinsApiConfig = {\n apiUrl: \"https://coins.talisman.xyz\",\n}\nexport async function fetchTokenRates(\n tokens: Record<TokenId, Token>,\n currencyIds: TokenRateCurrency[] = ALL_CURRENCY_IDS,\n config: CoinsApiConfig = DEFAULT_COINSAPI_CONFIG\n): Promise<TokenRatesList> {\n // create a map from `coingeckoId` -> `tokenId` for each token\n const coingeckoIdToTokenIds = Object.values(tokens)\n .flatMap((token) => {\n // BEGIN: LP tokens have a rate which is calculated later on, using the rates of two other tokens.\n //\n // This section contains the logic such that: if token is an LP token, then fetch the rates for the two underlying tokens.\n if (token.type === \"evm-uniswapv2\") {\n if (token.platform !== \"ethereum\") return []\n\n const getToken = (\n evmNetworkId: string,\n tokenAddress: `0x${string}`,\n coingeckoId: string\n ) => ({\n id: evmErc20TokenId(evmNetworkId, tokenAddress),\n coingeckoId,\n })\n\n const token0 = token.coingeckoId0\n ? [getToken(token.networkId, token.tokenAddress0, token.coingeckoId0)]\n : []\n const token1 = token.coingeckoId1\n ? [getToken(token.networkId, token.tokenAddress1, token.coingeckoId1)]\n : []\n\n return [...token0, ...token1]\n }\n // END: LP tokens have a rate which is calculated later on, using the rates of two other tokens.\n\n // ignore tokens which don't have a coingeckoId\n if (!token.coingeckoId) return []\n\n return [{ id: token.id, coingeckoId: token.coingeckoId }]\n })\n\n // get each token's coingeckoId\n .reduce(\n (coingeckoIdToTokenIds, { id, coingeckoId }) => {\n if (!coingeckoIdToTokenIds[coingeckoId]) coingeckoIdToTokenIds[coingeckoId] = []\n coingeckoIdToTokenIds[coingeckoId].push(id)\n return coingeckoIdToTokenIds\n },\n {} as Record<string, string[]>\n )\n\n // create a list of coingeckoIds we want to fetch\n const coingeckoIds = Object.keys(coingeckoIdToTokenIds).sort()\n\n // skip network request if there is nothing for us to fetch\n if (coingeckoIds.length < 1) return {}\n\n // If `currencyIds` includes `tao`, we need to always fetch the `bittensor` coingeckoId and the `usd` currency,\n // we can use these to calculate the currency rate for TAO relative to all other tokens.\n //\n // We support showing balances in TAO just like we support BTC/ETH/DOT, but coingecko doesn't support TAO as a vs currency rate.\n // We can macgyver our own TOKEN<>TAO rate by combining the TOKEN<>USD data with the TAO<>USD data.\n const hasVsTao = currencyIds.includes(\"tao\")\n const [effectiveCoingeckoIds, effectiveCurrencyIds] = hasVsTao\n ? [\n [...new Set(coingeckoIds).add(\"bittensor\")],\n [\n ...new Set(\n // don't request `tao` from coingecko (we calculate it from `usd`)\n currencyIds.filter((c) => c !== \"tao\")\n )\n // always include `usd` (so we can calculate `tao`)\n .add(\"usd\"),\n ],\n ]\n : [coingeckoIds, currencyIds]\n\n const response = await fetch(`${config.apiUrl}/token-rates`, {\n method: \"POST\",\n body: JSON.stringify({\n coingeckoIds: effectiveCoingeckoIds,\n currencyIds: effectiveCurrencyIds,\n }),\n })\n\n const rawTokenRates = (await response.json()) as RawTokenRates\n\n if (hasVsTao) {\n // calculate the TAO<>USD rate\n const effectiveTaoIndex = effectiveCoingeckoIds.indexOf(\"bittensor\")\n const effectiveUsdIndex = effectiveCurrencyIds.indexOf(\"usd\")\n const taoUsdRate = rawTokenRates[effectiveTaoIndex]?.[effectiveUsdIndex]?.[0]\n const taoUsdChange24h = rawTokenRates[effectiveTaoIndex]?.[effectiveUsdIndex]?.[2]\n\n // insert TOKEN<>TAO rate (calculated based on TAO<>USD rate and TOKEN<>USD rate) into each TOKEN\n const taoIndex = currencyIds.indexOf(\"tao\")\n rawTokenRates.forEach((rates, i) => {\n // hardcoded rate for TAO<>TAO\n if (i === effectiveTaoIndex) {\n rates?.splice(taoIndex, 0, [1, null, null])\n return\n }\n\n // get TOKEN<>USD rate\n const tokenUsdRate = rates?.[effectiveUsdIndex]?.[0]\n // calculate TOKEN<>TAO rate\n const tokenTaoRate =\n typeof tokenUsdRate === \"number\" && typeof taoUsdRate === \"number\" && taoUsdRate !== 0\n ? tokenUsdRate / taoUsdRate\n : null\n\n const tokenUsdChange24h = rates?.[effectiveUsdIndex]?.[2]\n const tokenTaoChange24h =\n typeof taoUsdChange24h === \"number\" && typeof tokenUsdChange24h === \"number\"\n ? (1 + tokenUsdChange24h) / (1 + taoUsdChange24h) - 1\n : null\n\n // insert at the correct location (based on the index of `tao` in `currencyIds`)\n rates?.splice(taoIndex, 0, [tokenTaoRate, null, tokenTaoChange24h])\n })\n }\n\n const tokenRates = parseTokenRatesFromApi(rawTokenRates, coingeckoIds, currencyIds)\n\n // build a TokenRatesList from the token prices result\n const ratesList: TokenRatesList = Object.fromEntries(\n Object.entries(tokens).map(([tokenId, token]) => [\n tokenId,\n token.coingeckoId ? (tokenRates[token.coingeckoId] ?? null) : null,\n ])\n ) as TokenRatesList\n\n return ratesList\n}\n\n// To save on bandwidth and work around response size limits, values are returned without json property names\n// (e.g. [[[12, 12332, 0.5]]] instead of { dot : {usd: { value: 12, marketCap: 12332, change24h: 0.5 }} })\ntype RawTokenRates = [number | null, number | null, number | null][][]\n\nconst parseTokenRatesFromApi = (\n rawTokenRates: RawTokenRates,\n coingeckoIds: string[],\n currencyIds: TokenRateCurrency[]\n): TokenRatesList => {\n return Object.fromEntries(\n coingeckoIds.map((coingeckoId, idx) => {\n const rates = rawTokenRates[idx]\n if (!rates) return [coingeckoId, null]\n\n return [\n coingeckoId,\n Object.fromEntries(\n currencyIds.map((currencyId, idx) => {\n const curRate = rates[idx]\n if (!curRate) return [currencyId, null]\n\n const [price, marketCap, change24h] = rates[idx]\n return [currencyId, { price, marketCap, change24h } as TokenRateData]\n })\n ) as TokenRates,\n ]\n })\n ) as TokenRatesList\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["packageJson.name","u16","u64"],"sources":["../package.json","../src/log.ts","../src/types.ts","../src/getDTaoTokenRates.ts","../src/fetchDTaoTokenRates.ts","../src/TalismanTokenRatesDatabase.ts","../src/TokenRates.ts"],"sourcesContent":["","import anylogger from \"anylogger\"\n\nimport packageJson from \"../package.json\"\n\nexport default anylogger(packageJson.name)\n","import type { TokenId } from \"@talismn/chaindata-provider\"\n\nexport const SUPPORTED_CURRENCIES = {\n btc: { name: \"Bitcoin\", symbol: \"₿\" },\n eth: { name: \"Ethereum\", symbol: \"Ξ\" },\n dot: { name: \"Polkadot\", symbol: \"D\" },\n tao: { name: \"Bittensor\", symbol: \"τ\" },\n\n usd: { name: \"US Dollar\", symbol: \"$\" },\n cny: { name: \"Chinese Yuan\", symbol: \"¥\" },\n eur: { name: \"Euro\", symbol: \"€\" },\n gbp: { name: \"British Pound\", symbol: \"£\" },\n cad: { name: \"Canadian Dollar\", symbol: \"C$\" },\n aud: { name: \"Australian Dollar\", symbol: \"A$\" },\n nzd: { name: \"New Zealand Dollar\", symbol: \"NZ$\" },\n jpy: { name: \"Japanese Yen\", symbol: \"¥\" },\n rub: { name: \"Russian Ruble\", symbol: \"₽\" },\n krw: { name: \"South Korean Won\", symbol: \"₩\" },\n idr: { name: \"Indonesian Rupiah\", symbol: \"Rp\" },\n php: { name: \"Philippine Peso\", symbol: \"₱\" },\n thb: { name: \"Thai Baht\", symbol: \"฿\" },\n vnd: { name: \"Vietnamese Dong\", symbol: \"₫\" },\n inr: { name: \"Indian Rupee\", symbol: \"₹\" },\n try: { name: \"Turkish Lira\", symbol: \"₺\" },\n // hkd: { name: \"Hong Kong Dollar\", symbol: \"HK$\" },\n sgd: { name: \"Singapore Dollar\", symbol: \"S$\" },\n // twd: { name: \"Taiwanese Dollar\", symbol: \"NT$\" },\n} as const\n\nexport type TokenRatesStorage = { tokenRates: TokenRatesList }\n\nexport type TokenRatesList = Record<TokenId, TokenRates>\nexport type TokenRates = Record<TokenRateCurrency, TokenRateData | null>\n\nexport type TokenRateData = { price: number; marketCap?: number; change24h?: number }\nexport type TokenRateCurrency = keyof typeof SUPPORTED_CURRENCIES\n\nexport const newTokenRates = (): TokenRates => ({\n btc: null,\n eth: null,\n dot: null,\n tao: null,\n\n usd: null,\n cny: null,\n eur: null,\n gbp: null,\n cad: null,\n aud: null,\n nzd: null,\n jpy: null,\n rub: null,\n krw: null,\n idr: null,\n php: null,\n thb: null,\n vnd: null,\n inr: null,\n try: null,\n // hkd: null,\n sgd: null,\n // twd: null,\n})\n","import { type SubDTaoToken, subNativeTokenId } from \"@talismn/chaindata-provider\"\n\nimport log from \"./log\"\nimport {\n newTokenRates,\n type TokenRateCurrency,\n type TokenRateData,\n type TokenRatesList,\n} from \"./types\"\n\n/** TAO-per-alpha pool price fixed-point scale: 10^9 (TAO decimals) */\nconst SCALED_ALPHA_PRICE_SCALE = 10 ** 9\n\n/**\n * 24h change of an alpha token in a given currency, in percent: the pool price change (alpha vs\n * TAO) compounded with the TAO change in that currency. Unknown without the pool change.\n */\nconst getAlphaChange24h = (\n currency: TokenRateCurrency,\n taoChange24h: number | null | undefined,\n alphaPriceChange24h: number | null | undefined\n): number | undefined => {\n if (typeof alphaPriceChange24h !== \"number\" || !Number.isFinite(alphaPriceChange24h))\n return undefined\n\n // TAO itself doesn't move against the tao currency: the alpha change is the pool change\n if (currency === \"tao\") return alphaPriceChange24h\n\n if (typeof taoChange24h !== \"number\" || !Number.isFinite(taoChange24h)) return undefined\n return ((1 + alphaPriceChange24h / 100) * (1 + taoChange24h / 100) - 1) * 100\n}\n\n/**\n * Rates of a bittensor dtao (subnet alpha) token, derived from its network's native TAO rates\n * scaled by the subnet pool price. To be used for tokens that don't have a coingecko id.\n *\n * @param token\n * @param tokenRates must contain the rates of the network's native (TAO) token\n * @param scaledAlphaPrice current TAO-per-alpha pool price, fixed-point scaled by 10^9 (rao)\n * @param alphaPriceChange24h 24h change of the pool price (alpha vs TAO) in percent, when known\n */\nexport const getDTaoTokenRates = (\n token: SubDTaoToken,\n tokenRates: TokenRatesList,\n scaledAlphaPrice: string | bigint,\n alphaPriceChange24h?: number | null\n) => {\n try {\n const taoTokenId = subNativeTokenId(token.networkId)\n const taoTokenRates = tokenRates[taoTokenId]\n if (!taoTokenRates) return null\n\n // for root subnet, same rates as TAO\n if (token.netuid === 0) return structuredClone(taoTokenRates)\n\n const priceRatio = Number(BigInt(scaledAlphaPrice)) / SCALED_ALPHA_PRICE_SCALE\n\n const alphaRates = newTokenRates()\n for (const [currency, taoRate] of Object.entries(taoTokenRates) as [\n TokenRateCurrency,\n TokenRateData | null,\n ][]) {\n if (!taoRate) {\n alphaRates[currency] = null\n } else {\n alphaRates[currency] = {\n price: taoRate.price * priceRatio,\n marketCap: taoRate.marketCap ? taoRate.marketCap * priceRatio : undefined,\n change24h: getAlphaChange24h(currency, taoRate.change24h, alphaPriceChange24h),\n }\n }\n }\n\n return alphaRates\n } catch (err) {\n log.error(\"Failed to compute dtao token rates\", err)\n return null\n }\n}\n","import type { IChainConnectorDot } from \"@talismn/chain-connectors\"\nimport type { NetworkId, SubDTaoToken, TokenList } from \"@talismn/chaindata-provider\"\nimport { subNativeTokenId } from \"@talismn/chaindata-provider\"\nimport { throwAfter } from \"@talismn/util\"\nimport { Struct, u16, u64, Vector } from \"scale-ts\"\n\nimport { getDTaoTokenRates } from \"./getDTaoTokenRates\"\nimport log from \"./log\"\nimport type { TokenRatesList } from \"./types\"\n\nconst DEFAULT_BITTENSOR_NETWORK_ID = \"bittensor\"\nconst DEFAULT_TAO_DATA_API_URL = \"https://tda.talisman.xyz\"\nconst DEFAULT_TIMEOUT_MS = 10_000\n\n// decoded shape of the SwapRuntimeApi_current_alpha_price_all result\nconst alphaPricesCodec = Vector(Struct({ netuid: u16, price: u64 }))\n\n/** Current TAO-per-alpha pool price of every subnet, keyed by netuid, in the scaledAlphaPrice fixed-point format (rao) */\nconst fetchScaledAlphaPricesByNetuid = async (\n connector: IChainConnectorDot,\n networkId: NetworkId\n): Promise<Map<number, bigint>> => {\n const result = await connector.send<string>(networkId, \"state_call\", [\n \"SwapRuntimeApi_current_alpha_price_all\",\n \"0x\",\n ])\n return new Map(alphaPricesCodec.dec(result).map(({ netuid, price }) => [netuid, price]))\n}\n\nconst ALPHA_PRICE_CHANGES_TTL = 5 * 60_000\nconst alphaPriceChangesCache = new Map<\n string,\n { fetchedAt: number; changes: Map<number, number> }\n>()\n\n/**\n * 24h percent change of every subnet pool price (alpha vs TAO) from the tao-data api — the\n * chain knows the current pool prices but not yesterday's. 5-min in-memory cache; failure is\n * non-fatal (stale cache when available, else no change24h on the computed rates).\n */\nconst getAlphaPriceChangesByNetuid = async (\n apiUrl: string,\n customFetch: typeof fetch,\n timeoutMs: number\n): Promise<Map<number, number> | null> => {\n const cached = alphaPriceChangesCache.get(apiUrl)\n if (cached && performance.now() - cached.fetchedAt < ALPHA_PRICE_CHANGES_TTL)\n return cached.changes\n\n try {\n const response = await customFetch(`${apiUrl}/pools`, {\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!response.ok) throw new Error(`${response.status} ${response.statusText}`)\n const pools = (await response.json()) as {\n netuid: number\n price_change_1_day: number | string\n }[]\n const changes = new Map(\n pools\n .map((pool): [number, number] => [pool.netuid, Number(pool.price_change_1_day)])\n .filter(([, change]) => Number.isFinite(change))\n )\n alphaPriceChangesCache.set(apiUrl, { fetchedAt: performance.now(), changes })\n return changes\n } catch (err) {\n log.warn(\"Failed to fetch alpha price 24h changes\", err)\n return cached?.changes ?? null\n }\n}\n\nexport type FetchDTaoTokenRatesOptions = {\n /** substrate chain connector of the host (only its `send` method is used, for one state_call per invocation) */\n connector: IChainConnectorDot\n /** the bittensor network id, defaults to \"bittensor\" (mainnet) */\n networkId?: NetworkId\n /** token list to price — non-dtao tokens (and dtao tokens with a coingeckoId) are ignored */\n tokens: TokenList\n /** freshly fetched rates list — dtao rates derive from the network's native TAO token rates in it */\n tokenRates: TokenRatesList\n /** previous rates list, used as keep-last fallback when fetching fails */\n previousRates: TokenRatesList\n /** custom fetch for the tao-data api (eg with auth headers), defaults to global fetch */\n customFetch?: typeof fetch\n /** tao-data api url, defaults to https://tda.talisman.xyz — pass null to skip the 24h-change fetch (eg testnets, which the api has no data for) */\n taoDataApiUrl?: string | null\n /** per-request timeout for the chain call and the tao-data api call, defaults to 10s */\n timeoutMs?: number\n}\n\n/**\n * Rates entries for the bittensor dtao (subnet alpha) tokens of `tokens`, priced from their\n * subnet pool combined with the TAO rates in `tokenRates`. On chain-fetch failure, falls\n * back to the token's entry in `previousRates` (keep-last). Never throws — a dtao pricing\n * failure must not discard the rates list it gets merged into.\n *\n * Hosts remain responsible for WHEN to call this (network enabled state, refresh cadence)\n * and for merging the result into their rates list/store.\n */\nexport const fetchDTaoTokenRates = async ({\n connector,\n networkId = DEFAULT_BITTENSOR_NETWORK_ID,\n tokens,\n tokenRates,\n previousRates,\n customFetch = fetch,\n taoDataApiUrl = DEFAULT_TAO_DATA_API_URL,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n}: FetchDTaoTokenRatesOptions): Promise<TokenRatesList> => {\n const dtaoTokens = Object.values(tokens).filter(\n (token): token is SubDTaoToken =>\n token.type === \"substrate-dtao\" && token.networkId === networkId && !token.coingeckoId\n )\n if (!dtaoTokens.length) return {}\n\n // alpha rates derive from the network's native TAO rates: without them there is nothing to\n // price against, and keep-last must not apply (stale entries would display as current)\n if (!tokenRates[subNativeTokenId(networkId)]) return {}\n\n // the two fetches are independent: run them concurrently\n const [prices, changes] = await Promise.all([\n Promise.race([\n fetchScaledAlphaPricesByNetuid(connector, networkId),\n throwAfter(timeoutMs, `Timed out after ${timeoutMs}ms`),\n ]).catch((err) => {\n log.warn(\"Failed to fetch alpha prices, keeping previous dtao rates\", err)\n return null\n }),\n taoDataApiUrl === null\n ? null\n : getAlphaPriceChangesByNetuid(taoDataApiUrl, customFetch, timeoutMs),\n ])\n\n if (!prices)\n return Object.fromEntries(\n dtaoTokens\n .filter((token) => previousRates[token.id])\n .map((token) => [token.id, previousRates[token.id]])\n )\n\n return Object.fromEntries(\n dtaoTokens\n .map((token) => {\n // never fabricate a zero price: a missing or zero pool price keeps the last rates\n const price = prices.get(token.netuid)\n const rates = price\n ? getDTaoTokenRates(token, tokenRates, price, changes?.get(token.netuid))\n : null\n return [token.id, rates ?? previousRates[token.id]] as const\n })\n .filter(([, rates]) => !!rates)\n )\n}\n","export const tryToDeleteOldTokenRatesDb = () => {\n try {\n // try and delete it if it's still there\n indexedDB.deleteDatabase(\"TalismanTokenRates\")\n } catch {\n // dont care if it fails\n }\n}\n","import { evmErc20TokenId, type Token, type TokenId } from \"@talismn/chaindata-provider\"\n\nimport {\n SUPPORTED_CURRENCIES,\n type TokenRateCurrency,\n type TokenRateData,\n type TokenRates,\n type TokenRatesList,\n} from \"./types\"\n\nexport class TokenRatesError extends Error {\n response?: Response\n constructor(message: string, response?: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport const ALL_CURRENCY_IDS = Object.keys(SUPPORTED_CURRENCIES) as TokenRateCurrency[]\nexport type CoinsApiConfig = {\n apiUrl: string\n}\n\nexport const DEFAULT_COINSAPI_CONFIG: CoinsApiConfig = {\n apiUrl: \"https://coins.talisman.xyz\",\n}\nexport async function fetchTokenRates(\n tokens: Record<TokenId, Token>,\n currencyIds: TokenRateCurrency[] = ALL_CURRENCY_IDS,\n config: CoinsApiConfig = DEFAULT_COINSAPI_CONFIG\n): Promise<TokenRatesList> {\n // create a map from `coingeckoId` -> `tokenId` for each token\n const coingeckoIdToTokenIds = Object.values(tokens)\n .flatMap((token) => {\n // BEGIN: LP tokens have a rate which is calculated later on, using the rates of two other tokens.\n //\n // This section contains the logic such that: if token is an LP token, then fetch the rates for the two underlying tokens.\n if (token.type === \"evm-uniswapv2\") {\n if (token.platform !== \"ethereum\") return []\n\n const getToken = (\n evmNetworkId: string,\n tokenAddress: `0x${string}`,\n coingeckoId: string\n ) => ({\n id: evmErc20TokenId(evmNetworkId, tokenAddress),\n coingeckoId,\n })\n\n const token0 = token.coingeckoId0\n ? [getToken(token.networkId, token.tokenAddress0, token.coingeckoId0)]\n : []\n const token1 = token.coingeckoId1\n ? [getToken(token.networkId, token.tokenAddress1, token.coingeckoId1)]\n : []\n\n return [...token0, ...token1]\n }\n // END: LP tokens have a rate which is calculated later on, using the rates of two other tokens.\n\n // ignore tokens which don't have a coingeckoId\n if (!token.coingeckoId) return []\n\n return [{ id: token.id, coingeckoId: token.coingeckoId }]\n })\n\n // get each token's coingeckoId\n .reduce(\n (coingeckoIdToTokenIds, { id, coingeckoId }) => {\n if (!coingeckoIdToTokenIds[coingeckoId]) coingeckoIdToTokenIds[coingeckoId] = []\n coingeckoIdToTokenIds[coingeckoId].push(id)\n return coingeckoIdToTokenIds\n },\n {} as Record<string, string[]>\n )\n\n // create a list of coingeckoIds we want to fetch\n const coingeckoIds = Object.keys(coingeckoIdToTokenIds).sort()\n\n // skip network request if there is nothing for us to fetch\n if (coingeckoIds.length < 1) return {}\n\n // If `currencyIds` includes `tao`, we need to always fetch the `bittensor` coingeckoId and the `usd` currency,\n // we can use these to calculate the currency rate for TAO relative to all other tokens.\n //\n // We support showing balances in TAO just like we support BTC/ETH/DOT, but coingecko doesn't support TAO as a vs currency rate.\n // We can macgyver our own TOKEN<>TAO rate by combining the TOKEN<>USD data with the TAO<>USD data.\n const hasVsTao = currencyIds.includes(\"tao\")\n const [effectiveCoingeckoIds, effectiveCurrencyIds] = hasVsTao\n ? [\n [...new Set(coingeckoIds).add(\"bittensor\")],\n [\n ...new Set(\n // don't request `tao` from coingecko (we calculate it from `usd`)\n currencyIds.filter((c) => c !== \"tao\")\n )\n // always include `usd` (so we can calculate `tao`)\n .add(\"usd\"),\n ],\n ]\n : [coingeckoIds, currencyIds]\n\n const response = await fetch(`${config.apiUrl}/token-rates`, {\n method: \"POST\",\n body: JSON.stringify({\n coingeckoIds: effectiveCoingeckoIds,\n currencyIds: effectiveCurrencyIds,\n }),\n })\n\n const rawTokenRates = (await response.json()) as RawTokenRates\n\n if (hasVsTao) {\n // calculate the TAO<>USD rate\n const effectiveTaoIndex = effectiveCoingeckoIds.indexOf(\"bittensor\")\n const effectiveUsdIndex = effectiveCurrencyIds.indexOf(\"usd\")\n const taoUsdRate = rawTokenRates[effectiveTaoIndex]?.[effectiveUsdIndex]?.[0]\n const taoUsdChange24h = rawTokenRates[effectiveTaoIndex]?.[effectiveUsdIndex]?.[2]\n\n // insert TOKEN<>TAO rate (calculated based on TAO<>USD rate and TOKEN<>USD rate) into each TOKEN\n const taoIndex = currencyIds.indexOf(\"tao\")\n rawTokenRates.forEach((rates, i) => {\n // hardcoded rate for TAO<>TAO\n if (i === effectiveTaoIndex) {\n rates?.splice(taoIndex, 0, [1, null, null])\n return\n }\n\n // get TOKEN<>USD rate\n const tokenUsdRate = rates?.[effectiveUsdIndex]?.[0]\n // calculate TOKEN<>TAO rate\n const tokenTaoRate =\n typeof tokenUsdRate === \"number\" && typeof taoUsdRate === \"number\" && taoUsdRate !== 0\n ? tokenUsdRate / taoUsdRate\n : null\n\n const tokenUsdChange24h = rates?.[effectiveUsdIndex]?.[2]\n const tokenTaoChange24h =\n typeof taoUsdChange24h === \"number\" && typeof tokenUsdChange24h === \"number\"\n ? (1 + tokenUsdChange24h) / (1 + taoUsdChange24h) - 1\n : null\n\n // insert at the correct location (based on the index of `tao` in `currencyIds`)\n rates?.splice(taoIndex, 0, [tokenTaoRate, null, tokenTaoChange24h])\n })\n }\n\n const tokenRates = parseTokenRatesFromApi(rawTokenRates, coingeckoIds, currencyIds)\n\n // build a TokenRatesList from the token prices result\n const ratesList: TokenRatesList = Object.fromEntries(\n Object.entries(tokens).map(([tokenId, token]) => [\n tokenId,\n token.coingeckoId ? (tokenRates[token.coingeckoId] ?? null) : null,\n ])\n ) as TokenRatesList\n\n return ratesList\n}\n\n// To save on bandwidth and work around response size limits, values are returned without json property names\n// (e.g. [[[12, 12332, 0.5]]] instead of { dot : {usd: { value: 12, marketCap: 12332, change24h: 0.5 }} })\ntype RawTokenRates = [number | null, number | null, number | null][][]\n\nconst parseTokenRatesFromApi = (\n rawTokenRates: RawTokenRates,\n coingeckoIds: string[],\n currencyIds: TokenRateCurrency[]\n): TokenRatesList => {\n return Object.fromEntries(\n coingeckoIds.map((coingeckoId, idx) => {\n const rates = rawTokenRates[idx]\n if (!rates) return [coingeckoId, null]\n\n return [\n coingeckoId,\n Object.fromEntries(\n currencyIds.map((currencyId, idx) => {\n const curRate = rates[idx]\n if (!curRate) return [currencyId, null]\n\n const [price, marketCap, change24h] = rates[idx]\n return [currencyId, { price, marketCap, change24h } as TokenRateData]\n })\n ) as TokenRates,\n ]\n })\n ) as TokenRatesList\n}\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -213,7 +213,7 @@ const fetchDTaoTokenRates = async ({ connector, networkId = DEFAULT_BITTENSOR_NE
|
|
|
213
213
|
const [prices, changes] = await Promise.all([Promise.race([fetchScaledAlphaPricesByNetuid(connector, networkId), throwAfter(timeoutMs, `Timed out after ${timeoutMs}ms`)]).catch((err) => {
|
|
214
214
|
log_default.warn("Failed to fetch alpha prices, keeping previous dtao rates", err);
|
|
215
215
|
return null;
|
|
216
|
-
}), getAlphaPriceChangesByNetuid(taoDataApiUrl, customFetch, timeoutMs)]);
|
|
216
|
+
}), taoDataApiUrl === null ? null : getAlphaPriceChangesByNetuid(taoDataApiUrl, customFetch, timeoutMs)]);
|
|
217
217
|
if (!prices) return Object.fromEntries(dtaoTokens.filter((token) => previousRates[token.id]).map((token) => [token.id, previousRates[token.id]]));
|
|
218
218
|
return Object.fromEntries(dtaoTokens.map((token) => {
|
|
219
219
|
const price = prices.get(token.netuid);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["packageJson.name"],"sources":["../package.json","../src/log.ts","../src/types.ts","../src/getDTaoTokenRates.ts","../src/fetchDTaoTokenRates.ts","../src/TalismanTokenRatesDatabase.ts","../src/TokenRates.ts"],"sourcesContent":["","import anylogger from \"anylogger\"\n\nimport packageJson from \"../package.json\"\n\nexport default anylogger(packageJson.name)\n","import type { TokenId } from \"@talismn/chaindata-provider\"\n\nexport const SUPPORTED_CURRENCIES = {\n btc: { name: \"Bitcoin\", symbol: \"₿\" },\n eth: { name: \"Ethereum\", symbol: \"Ξ\" },\n dot: { name: \"Polkadot\", symbol: \"D\" },\n tao: { name: \"Bittensor\", symbol: \"τ\" },\n\n usd: { name: \"US Dollar\", symbol: \"$\" },\n cny: { name: \"Chinese Yuan\", symbol: \"¥\" },\n eur: { name: \"Euro\", symbol: \"€\" },\n gbp: { name: \"British Pound\", symbol: \"£\" },\n cad: { name: \"Canadian Dollar\", symbol: \"C$\" },\n aud: { name: \"Australian Dollar\", symbol: \"A$\" },\n nzd: { name: \"New Zealand Dollar\", symbol: \"NZ$\" },\n jpy: { name: \"Japanese Yen\", symbol: \"¥\" },\n rub: { name: \"Russian Ruble\", symbol: \"₽\" },\n krw: { name: \"South Korean Won\", symbol: \"₩\" },\n idr: { name: \"Indonesian Rupiah\", symbol: \"Rp\" },\n php: { name: \"Philippine Peso\", symbol: \"₱\" },\n thb: { name: \"Thai Baht\", symbol: \"฿\" },\n vnd: { name: \"Vietnamese Dong\", symbol: \"₫\" },\n inr: { name: \"Indian Rupee\", symbol: \"₹\" },\n try: { name: \"Turkish Lira\", symbol: \"₺\" },\n // hkd: { name: \"Hong Kong Dollar\", symbol: \"HK$\" },\n sgd: { name: \"Singapore Dollar\", symbol: \"S$\" },\n // twd: { name: \"Taiwanese Dollar\", symbol: \"NT$\" },\n} as const\n\nexport type TokenRatesStorage = { tokenRates: TokenRatesList }\n\nexport type TokenRatesList = Record<TokenId, TokenRates>\nexport type TokenRates = Record<TokenRateCurrency, TokenRateData | null>\n\nexport type TokenRateData = { price: number; marketCap?: number; change24h?: number }\nexport type TokenRateCurrency = keyof typeof SUPPORTED_CURRENCIES\n\nexport const newTokenRates = (): TokenRates => ({\n btc: null,\n eth: null,\n dot: null,\n tao: null,\n\n usd: null,\n cny: null,\n eur: null,\n gbp: null,\n cad: null,\n aud: null,\n nzd: null,\n jpy: null,\n rub: null,\n krw: null,\n idr: null,\n php: null,\n thb: null,\n vnd: null,\n inr: null,\n try: null,\n // hkd: null,\n sgd: null,\n // twd: null,\n})\n","import { type SubDTaoToken, subNativeTokenId } from \"@talismn/chaindata-provider\"\n\nimport log from \"./log\"\nimport {\n newTokenRates,\n type TokenRateCurrency,\n type TokenRateData,\n type TokenRatesList,\n} from \"./types\"\n\n/** TAO-per-alpha pool price fixed-point scale: 10^9 (TAO decimals) */\nconst SCALED_ALPHA_PRICE_SCALE = 10 ** 9\n\n/**\n * 24h change of an alpha token in a given currency, in percent: the pool price change (alpha vs\n * TAO) compounded with the TAO change in that currency. Unknown without the pool change.\n */\nconst getAlphaChange24h = (\n currency: TokenRateCurrency,\n taoChange24h: number | null | undefined,\n alphaPriceChange24h: number | null | undefined\n): number | undefined => {\n if (typeof alphaPriceChange24h !== \"number\" || !Number.isFinite(alphaPriceChange24h))\n return undefined\n\n // TAO itself doesn't move against the tao currency: the alpha change is the pool change\n if (currency === \"tao\") return alphaPriceChange24h\n\n if (typeof taoChange24h !== \"number\" || !Number.isFinite(taoChange24h)) return undefined\n return ((1 + alphaPriceChange24h / 100) * (1 + taoChange24h / 100) - 1) * 100\n}\n\n/**\n * Rates of a bittensor dtao (subnet alpha) token, derived from its network's native TAO rates\n * scaled by the subnet pool price. To be used for tokens that don't have a coingecko id.\n *\n * @param token\n * @param tokenRates must contain the rates of the network's native (TAO) token\n * @param scaledAlphaPrice current TAO-per-alpha pool price, fixed-point scaled by 10^9 (rao)\n * @param alphaPriceChange24h 24h change of the pool price (alpha vs TAO) in percent, when known\n */\nexport const getDTaoTokenRates = (\n token: SubDTaoToken,\n tokenRates: TokenRatesList,\n scaledAlphaPrice: string | bigint,\n alphaPriceChange24h?: number | null\n) => {\n try {\n const taoTokenId = subNativeTokenId(token.networkId)\n const taoTokenRates = tokenRates[taoTokenId]\n if (!taoTokenRates) return null\n\n // for root subnet, same rates as TAO\n if (token.netuid === 0) return structuredClone(taoTokenRates)\n\n const priceRatio = Number(BigInt(scaledAlphaPrice)) / SCALED_ALPHA_PRICE_SCALE\n\n const alphaRates = newTokenRates()\n for (const [currency, taoRate] of Object.entries(taoTokenRates) as [\n TokenRateCurrency,\n TokenRateData | null,\n ][]) {\n if (!taoRate) {\n alphaRates[currency] = null\n } else {\n alphaRates[currency] = {\n price: taoRate.price * priceRatio,\n marketCap: taoRate.marketCap ? taoRate.marketCap * priceRatio : undefined,\n change24h: getAlphaChange24h(currency, taoRate.change24h, alphaPriceChange24h),\n }\n }\n }\n\n return alphaRates\n } catch (err) {\n log.error(\"Failed to compute dtao token rates\", err)\n return null\n }\n}\n","import type { IChainConnectorDot } from \"@talismn/chain-connectors\"\nimport type { NetworkId, SubDTaoToken, TokenList } from \"@talismn/chaindata-provider\"\nimport { subNativeTokenId } from \"@talismn/chaindata-provider\"\nimport { throwAfter } from \"@talismn/util\"\nimport { Struct, u16, u64, Vector } from \"scale-ts\"\n\nimport { getDTaoTokenRates } from \"./getDTaoTokenRates\"\nimport log from \"./log\"\nimport type { TokenRatesList } from \"./types\"\n\nconst DEFAULT_BITTENSOR_NETWORK_ID = \"bittensor\"\nconst DEFAULT_TAO_DATA_API_URL = \"https://tda.talisman.xyz\"\nconst DEFAULT_TIMEOUT_MS = 10_000\n\n// decoded shape of the SwapRuntimeApi_current_alpha_price_all result\nconst alphaPricesCodec = Vector(Struct({ netuid: u16, price: u64 }))\n\n/** Current TAO-per-alpha pool price of every subnet, keyed by netuid, in the scaledAlphaPrice fixed-point format (rao) */\nconst fetchScaledAlphaPricesByNetuid = async (\n connector: IChainConnectorDot,\n networkId: NetworkId\n): Promise<Map<number, bigint>> => {\n const result = await connector.send<string>(networkId, \"state_call\", [\n \"SwapRuntimeApi_current_alpha_price_all\",\n \"0x\",\n ])\n return new Map(alphaPricesCodec.dec(result).map(({ netuid, price }) => [netuid, price]))\n}\n\nconst ALPHA_PRICE_CHANGES_TTL = 5 * 60_000\nconst alphaPriceChangesCache = new Map<\n string,\n { fetchedAt: number; changes: Map<number, number> }\n>()\n\n/**\n * 24h percent change of every subnet pool price (alpha vs TAO) from the tao-data api — the\n * chain knows the current pool prices but not yesterday's. 5-min in-memory cache; failure is\n * non-fatal (stale cache when available, else no change24h on the computed rates).\n */\nconst getAlphaPriceChangesByNetuid = async (\n apiUrl: string,\n customFetch: typeof fetch,\n timeoutMs: number\n): Promise<Map<number, number> | null> => {\n const cached = alphaPriceChangesCache.get(apiUrl)\n if (cached && performance.now() - cached.fetchedAt < ALPHA_PRICE_CHANGES_TTL)\n return cached.changes\n\n try {\n const response = await customFetch(`${apiUrl}/pools`, {\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!response.ok) throw new Error(`${response.status} ${response.statusText}`)\n const pools = (await response.json()) as {\n netuid: number\n price_change_1_day: number | string\n }[]\n const changes = new Map(\n pools\n .map((pool): [number, number] => [pool.netuid, Number(pool.price_change_1_day)])\n .filter(([, change]) => Number.isFinite(change))\n )\n alphaPriceChangesCache.set(apiUrl, { fetchedAt: performance.now(), changes })\n return changes\n } catch (err) {\n log.warn(\"Failed to fetch alpha price 24h changes\", err)\n return cached?.changes ?? null\n }\n}\n\nexport type FetchDTaoTokenRatesOptions = {\n /** substrate chain connector of the host (only its `send` method is used, for one state_call per invocation) */\n connector: IChainConnectorDot\n /** the bittensor network id, defaults to \"bittensor\" (mainnet) */\n networkId?: NetworkId\n /** token list to price — non-dtao tokens (and dtao tokens with a coingeckoId) are ignored */\n tokens: TokenList\n /** freshly fetched rates list — dtao rates derive from the network's native TAO token rates in it */\n tokenRates: TokenRatesList\n /** previous rates list, used as keep-last fallback when fetching fails */\n previousRates: TokenRatesList\n /** custom fetch for the tao-data api (eg with auth headers), defaults to global fetch */\n customFetch?: typeof fetch\n /** tao-data api url, defaults to https://tda.talisman.xyz */\n taoDataApiUrl?: string\n /** per-request timeout for the chain call and the tao-data api call, defaults to 10s */\n timeoutMs?: number\n}\n\n/**\n * Rates entries for the bittensor dtao (subnet alpha) tokens of `tokens`, priced from their\n * subnet pool combined with the TAO rates in `tokenRates`. On chain-fetch failure, falls\n * back to the token's entry in `previousRates` (keep-last). Never throws — a dtao pricing\n * failure must not discard the rates list it gets merged into.\n *\n * Hosts remain responsible for WHEN to call this (network enabled state, refresh cadence)\n * and for merging the result into their rates list/store.\n */\nexport const fetchDTaoTokenRates = async ({\n connector,\n networkId = DEFAULT_BITTENSOR_NETWORK_ID,\n tokens,\n tokenRates,\n previousRates,\n customFetch = fetch,\n taoDataApiUrl = DEFAULT_TAO_DATA_API_URL,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n}: FetchDTaoTokenRatesOptions): Promise<TokenRatesList> => {\n const dtaoTokens = Object.values(tokens).filter(\n (token): token is SubDTaoToken =>\n token.type === \"substrate-dtao\" && token.networkId === networkId && !token.coingeckoId\n )\n if (!dtaoTokens.length) return {}\n\n // alpha rates derive from the network's native TAO rates: without them there is nothing to\n // price against, and keep-last must not apply (stale entries would display as current)\n if (!tokenRates[subNativeTokenId(networkId)]) return {}\n\n // the two fetches are independent: run them concurrently\n const [prices, changes] = await Promise.all([\n Promise.race([\n fetchScaledAlphaPricesByNetuid(connector, networkId),\n throwAfter(timeoutMs, `Timed out after ${timeoutMs}ms`),\n ]).catch((err) => {\n log.warn(\"Failed to fetch alpha prices, keeping previous dtao rates\", err)\n return null\n }),\n getAlphaPriceChangesByNetuid(taoDataApiUrl, customFetch, timeoutMs),\n ])\n\n if (!prices)\n return Object.fromEntries(\n dtaoTokens\n .filter((token) => previousRates[token.id])\n .map((token) => [token.id, previousRates[token.id]])\n )\n\n return Object.fromEntries(\n dtaoTokens\n .map((token) => {\n // never fabricate a zero price: a missing or zero pool price keeps the last rates\n const price = prices.get(token.netuid)\n const rates = price\n ? getDTaoTokenRates(token, tokenRates, price, changes?.get(token.netuid))\n : null\n return [token.id, rates ?? previousRates[token.id]] as const\n })\n .filter(([, rates]) => !!rates)\n )\n}\n","export const tryToDeleteOldTokenRatesDb = () => {\n try {\n // try and delete it if it's still there\n indexedDB.deleteDatabase(\"TalismanTokenRates\")\n } catch {\n // dont care if it fails\n }\n}\n","import { evmErc20TokenId, type Token, type TokenId } from \"@talismn/chaindata-provider\"\n\nimport {\n SUPPORTED_CURRENCIES,\n type TokenRateCurrency,\n type TokenRateData,\n type TokenRates,\n type TokenRatesList,\n} from \"./types\"\n\nexport class TokenRatesError extends Error {\n response?: Response\n constructor(message: string, response?: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport const ALL_CURRENCY_IDS = Object.keys(SUPPORTED_CURRENCIES) as TokenRateCurrency[]\nexport type CoinsApiConfig = {\n apiUrl: string\n}\n\nexport const DEFAULT_COINSAPI_CONFIG: CoinsApiConfig = {\n apiUrl: \"https://coins.talisman.xyz\",\n}\nexport async function fetchTokenRates(\n tokens: Record<TokenId, Token>,\n currencyIds: TokenRateCurrency[] = ALL_CURRENCY_IDS,\n config: CoinsApiConfig = DEFAULT_COINSAPI_CONFIG\n): Promise<TokenRatesList> {\n // create a map from `coingeckoId` -> `tokenId` for each token\n const coingeckoIdToTokenIds = Object.values(tokens)\n .flatMap((token) => {\n // BEGIN: LP tokens have a rate which is calculated later on, using the rates of two other tokens.\n //\n // This section contains the logic such that: if token is an LP token, then fetch the rates for the two underlying tokens.\n if (token.type === \"evm-uniswapv2\") {\n if (token.platform !== \"ethereum\") return []\n\n const getToken = (\n evmNetworkId: string,\n tokenAddress: `0x${string}`,\n coingeckoId: string\n ) => ({\n id: evmErc20TokenId(evmNetworkId, tokenAddress),\n coingeckoId,\n })\n\n const token0 = token.coingeckoId0\n ? [getToken(token.networkId, token.tokenAddress0, token.coingeckoId0)]\n : []\n const token1 = token.coingeckoId1\n ? [getToken(token.networkId, token.tokenAddress1, token.coingeckoId1)]\n : []\n\n return [...token0, ...token1]\n }\n // END: LP tokens have a rate which is calculated later on, using the rates of two other tokens.\n\n // ignore tokens which don't have a coingeckoId\n if (!token.coingeckoId) return []\n\n return [{ id: token.id, coingeckoId: token.coingeckoId }]\n })\n\n // get each token's coingeckoId\n .reduce(\n (coingeckoIdToTokenIds, { id, coingeckoId }) => {\n if (!coingeckoIdToTokenIds[coingeckoId]) coingeckoIdToTokenIds[coingeckoId] = []\n coingeckoIdToTokenIds[coingeckoId].push(id)\n return coingeckoIdToTokenIds\n },\n {} as Record<string, string[]>\n )\n\n // create a list of coingeckoIds we want to fetch\n const coingeckoIds = Object.keys(coingeckoIdToTokenIds).sort()\n\n // skip network request if there is nothing for us to fetch\n if (coingeckoIds.length < 1) return {}\n\n // If `currencyIds` includes `tao`, we need to always fetch the `bittensor` coingeckoId and the `usd` currency,\n // we can use these to calculate the currency rate for TAO relative to all other tokens.\n //\n // We support showing balances in TAO just like we support BTC/ETH/DOT, but coingecko doesn't support TAO as a vs currency rate.\n // We can macgyver our own TOKEN<>TAO rate by combining the TOKEN<>USD data with the TAO<>USD data.\n const hasVsTao = currencyIds.includes(\"tao\")\n const [effectiveCoingeckoIds, effectiveCurrencyIds] = hasVsTao\n ? [\n [...new Set(coingeckoIds).add(\"bittensor\")],\n [\n ...new Set(\n // don't request `tao` from coingecko (we calculate it from `usd`)\n currencyIds.filter((c) => c !== \"tao\")\n )\n // always include `usd` (so we can calculate `tao`)\n .add(\"usd\"),\n ],\n ]\n : [coingeckoIds, currencyIds]\n\n const response = await fetch(`${config.apiUrl}/token-rates`, {\n method: \"POST\",\n body: JSON.stringify({\n coingeckoIds: effectiveCoingeckoIds,\n currencyIds: effectiveCurrencyIds,\n }),\n })\n\n const rawTokenRates = (await response.json()) as RawTokenRates\n\n if (hasVsTao) {\n // calculate the TAO<>USD rate\n const effectiveTaoIndex = effectiveCoingeckoIds.indexOf(\"bittensor\")\n const effectiveUsdIndex = effectiveCurrencyIds.indexOf(\"usd\")\n const taoUsdRate = rawTokenRates[effectiveTaoIndex]?.[effectiveUsdIndex]?.[0]\n const taoUsdChange24h = rawTokenRates[effectiveTaoIndex]?.[effectiveUsdIndex]?.[2]\n\n // insert TOKEN<>TAO rate (calculated based on TAO<>USD rate and TOKEN<>USD rate) into each TOKEN\n const taoIndex = currencyIds.indexOf(\"tao\")\n rawTokenRates.forEach((rates, i) => {\n // hardcoded rate for TAO<>TAO\n if (i === effectiveTaoIndex) {\n rates?.splice(taoIndex, 0, [1, null, null])\n return\n }\n\n // get TOKEN<>USD rate\n const tokenUsdRate = rates?.[effectiveUsdIndex]?.[0]\n // calculate TOKEN<>TAO rate\n const tokenTaoRate =\n typeof tokenUsdRate === \"number\" && typeof taoUsdRate === \"number\" && taoUsdRate !== 0\n ? tokenUsdRate / taoUsdRate\n : null\n\n const tokenUsdChange24h = rates?.[effectiveUsdIndex]?.[2]\n const tokenTaoChange24h =\n typeof taoUsdChange24h === \"number\" && typeof tokenUsdChange24h === \"number\"\n ? (1 + tokenUsdChange24h) / (1 + taoUsdChange24h) - 1\n : null\n\n // insert at the correct location (based on the index of `tao` in `currencyIds`)\n rates?.splice(taoIndex, 0, [tokenTaoRate, null, tokenTaoChange24h])\n })\n }\n\n const tokenRates = parseTokenRatesFromApi(rawTokenRates, coingeckoIds, currencyIds)\n\n // build a TokenRatesList from the token prices result\n const ratesList: TokenRatesList = Object.fromEntries(\n Object.entries(tokens).map(([tokenId, token]) => [\n tokenId,\n token.coingeckoId ? (tokenRates[token.coingeckoId] ?? null) : null,\n ])\n ) as TokenRatesList\n\n return ratesList\n}\n\n// To save on bandwidth and work around response size limits, values are returned without json property names\n// (e.g. [[[12, 12332, 0.5]]] instead of { dot : {usd: { value: 12, marketCap: 12332, change24h: 0.5 }} })\ntype RawTokenRates = [number | null, number | null, number | null][][]\n\nconst parseTokenRatesFromApi = (\n rawTokenRates: RawTokenRates,\n coingeckoIds: string[],\n currencyIds: TokenRateCurrency[]\n): TokenRatesList => {\n return Object.fromEntries(\n coingeckoIds.map((coingeckoId, idx) => {\n const rates = rawTokenRates[idx]\n if (!rates) return [coingeckoId, null]\n\n return [\n coingeckoId,\n Object.fromEntries(\n currencyIds.map((currencyId, idx) => {\n const curRate = rates[idx]\n if (!curRate) return [currencyId, null]\n\n const [price, marketCap, change24h] = rates[idx]\n return [currencyId, { price, marketCap, change24h } as TokenRateData]\n })\n ) as TokenRates,\n ]\n })\n ) as TokenRatesList\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["packageJson.name"],"sources":["../package.json","../src/log.ts","../src/types.ts","../src/getDTaoTokenRates.ts","../src/fetchDTaoTokenRates.ts","../src/TalismanTokenRatesDatabase.ts","../src/TokenRates.ts"],"sourcesContent":["","import anylogger from \"anylogger\"\n\nimport packageJson from \"../package.json\"\n\nexport default anylogger(packageJson.name)\n","import type { TokenId } from \"@talismn/chaindata-provider\"\n\nexport const SUPPORTED_CURRENCIES = {\n btc: { name: \"Bitcoin\", symbol: \"₿\" },\n eth: { name: \"Ethereum\", symbol: \"Ξ\" },\n dot: { name: \"Polkadot\", symbol: \"D\" },\n tao: { name: \"Bittensor\", symbol: \"τ\" },\n\n usd: { name: \"US Dollar\", symbol: \"$\" },\n cny: { name: \"Chinese Yuan\", symbol: \"¥\" },\n eur: { name: \"Euro\", symbol: \"€\" },\n gbp: { name: \"British Pound\", symbol: \"£\" },\n cad: { name: \"Canadian Dollar\", symbol: \"C$\" },\n aud: { name: \"Australian Dollar\", symbol: \"A$\" },\n nzd: { name: \"New Zealand Dollar\", symbol: \"NZ$\" },\n jpy: { name: \"Japanese Yen\", symbol: \"¥\" },\n rub: { name: \"Russian Ruble\", symbol: \"₽\" },\n krw: { name: \"South Korean Won\", symbol: \"₩\" },\n idr: { name: \"Indonesian Rupiah\", symbol: \"Rp\" },\n php: { name: \"Philippine Peso\", symbol: \"₱\" },\n thb: { name: \"Thai Baht\", symbol: \"฿\" },\n vnd: { name: \"Vietnamese Dong\", symbol: \"₫\" },\n inr: { name: \"Indian Rupee\", symbol: \"₹\" },\n try: { name: \"Turkish Lira\", symbol: \"₺\" },\n // hkd: { name: \"Hong Kong Dollar\", symbol: \"HK$\" },\n sgd: { name: \"Singapore Dollar\", symbol: \"S$\" },\n // twd: { name: \"Taiwanese Dollar\", symbol: \"NT$\" },\n} as const\n\nexport type TokenRatesStorage = { tokenRates: TokenRatesList }\n\nexport type TokenRatesList = Record<TokenId, TokenRates>\nexport type TokenRates = Record<TokenRateCurrency, TokenRateData | null>\n\nexport type TokenRateData = { price: number; marketCap?: number; change24h?: number }\nexport type TokenRateCurrency = keyof typeof SUPPORTED_CURRENCIES\n\nexport const newTokenRates = (): TokenRates => ({\n btc: null,\n eth: null,\n dot: null,\n tao: null,\n\n usd: null,\n cny: null,\n eur: null,\n gbp: null,\n cad: null,\n aud: null,\n nzd: null,\n jpy: null,\n rub: null,\n krw: null,\n idr: null,\n php: null,\n thb: null,\n vnd: null,\n inr: null,\n try: null,\n // hkd: null,\n sgd: null,\n // twd: null,\n})\n","import { type SubDTaoToken, subNativeTokenId } from \"@talismn/chaindata-provider\"\n\nimport log from \"./log\"\nimport {\n newTokenRates,\n type TokenRateCurrency,\n type TokenRateData,\n type TokenRatesList,\n} from \"./types\"\n\n/** TAO-per-alpha pool price fixed-point scale: 10^9 (TAO decimals) */\nconst SCALED_ALPHA_PRICE_SCALE = 10 ** 9\n\n/**\n * 24h change of an alpha token in a given currency, in percent: the pool price change (alpha vs\n * TAO) compounded with the TAO change in that currency. Unknown without the pool change.\n */\nconst getAlphaChange24h = (\n currency: TokenRateCurrency,\n taoChange24h: number | null | undefined,\n alphaPriceChange24h: number | null | undefined\n): number | undefined => {\n if (typeof alphaPriceChange24h !== \"number\" || !Number.isFinite(alphaPriceChange24h))\n return undefined\n\n // TAO itself doesn't move against the tao currency: the alpha change is the pool change\n if (currency === \"tao\") return alphaPriceChange24h\n\n if (typeof taoChange24h !== \"number\" || !Number.isFinite(taoChange24h)) return undefined\n return ((1 + alphaPriceChange24h / 100) * (1 + taoChange24h / 100) - 1) * 100\n}\n\n/**\n * Rates of a bittensor dtao (subnet alpha) token, derived from its network's native TAO rates\n * scaled by the subnet pool price. To be used for tokens that don't have a coingecko id.\n *\n * @param token\n * @param tokenRates must contain the rates of the network's native (TAO) token\n * @param scaledAlphaPrice current TAO-per-alpha pool price, fixed-point scaled by 10^9 (rao)\n * @param alphaPriceChange24h 24h change of the pool price (alpha vs TAO) in percent, when known\n */\nexport const getDTaoTokenRates = (\n token: SubDTaoToken,\n tokenRates: TokenRatesList,\n scaledAlphaPrice: string | bigint,\n alphaPriceChange24h?: number | null\n) => {\n try {\n const taoTokenId = subNativeTokenId(token.networkId)\n const taoTokenRates = tokenRates[taoTokenId]\n if (!taoTokenRates) return null\n\n // for root subnet, same rates as TAO\n if (token.netuid === 0) return structuredClone(taoTokenRates)\n\n const priceRatio = Number(BigInt(scaledAlphaPrice)) / SCALED_ALPHA_PRICE_SCALE\n\n const alphaRates = newTokenRates()\n for (const [currency, taoRate] of Object.entries(taoTokenRates) as [\n TokenRateCurrency,\n TokenRateData | null,\n ][]) {\n if (!taoRate) {\n alphaRates[currency] = null\n } else {\n alphaRates[currency] = {\n price: taoRate.price * priceRatio,\n marketCap: taoRate.marketCap ? taoRate.marketCap * priceRatio : undefined,\n change24h: getAlphaChange24h(currency, taoRate.change24h, alphaPriceChange24h),\n }\n }\n }\n\n return alphaRates\n } catch (err) {\n log.error(\"Failed to compute dtao token rates\", err)\n return null\n }\n}\n","import type { IChainConnectorDot } from \"@talismn/chain-connectors\"\nimport type { NetworkId, SubDTaoToken, TokenList } from \"@talismn/chaindata-provider\"\nimport { subNativeTokenId } from \"@talismn/chaindata-provider\"\nimport { throwAfter } from \"@talismn/util\"\nimport { Struct, u16, u64, Vector } from \"scale-ts\"\n\nimport { getDTaoTokenRates } from \"./getDTaoTokenRates\"\nimport log from \"./log\"\nimport type { TokenRatesList } from \"./types\"\n\nconst DEFAULT_BITTENSOR_NETWORK_ID = \"bittensor\"\nconst DEFAULT_TAO_DATA_API_URL = \"https://tda.talisman.xyz\"\nconst DEFAULT_TIMEOUT_MS = 10_000\n\n// decoded shape of the SwapRuntimeApi_current_alpha_price_all result\nconst alphaPricesCodec = Vector(Struct({ netuid: u16, price: u64 }))\n\n/** Current TAO-per-alpha pool price of every subnet, keyed by netuid, in the scaledAlphaPrice fixed-point format (rao) */\nconst fetchScaledAlphaPricesByNetuid = async (\n connector: IChainConnectorDot,\n networkId: NetworkId\n): Promise<Map<number, bigint>> => {\n const result = await connector.send<string>(networkId, \"state_call\", [\n \"SwapRuntimeApi_current_alpha_price_all\",\n \"0x\",\n ])\n return new Map(alphaPricesCodec.dec(result).map(({ netuid, price }) => [netuid, price]))\n}\n\nconst ALPHA_PRICE_CHANGES_TTL = 5 * 60_000\nconst alphaPriceChangesCache = new Map<\n string,\n { fetchedAt: number; changes: Map<number, number> }\n>()\n\n/**\n * 24h percent change of every subnet pool price (alpha vs TAO) from the tao-data api — the\n * chain knows the current pool prices but not yesterday's. 5-min in-memory cache; failure is\n * non-fatal (stale cache when available, else no change24h on the computed rates).\n */\nconst getAlphaPriceChangesByNetuid = async (\n apiUrl: string,\n customFetch: typeof fetch,\n timeoutMs: number\n): Promise<Map<number, number> | null> => {\n const cached = alphaPriceChangesCache.get(apiUrl)\n if (cached && performance.now() - cached.fetchedAt < ALPHA_PRICE_CHANGES_TTL)\n return cached.changes\n\n try {\n const response = await customFetch(`${apiUrl}/pools`, {\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!response.ok) throw new Error(`${response.status} ${response.statusText}`)\n const pools = (await response.json()) as {\n netuid: number\n price_change_1_day: number | string\n }[]\n const changes = new Map(\n pools\n .map((pool): [number, number] => [pool.netuid, Number(pool.price_change_1_day)])\n .filter(([, change]) => Number.isFinite(change))\n )\n alphaPriceChangesCache.set(apiUrl, { fetchedAt: performance.now(), changes })\n return changes\n } catch (err) {\n log.warn(\"Failed to fetch alpha price 24h changes\", err)\n return cached?.changes ?? null\n }\n}\n\nexport type FetchDTaoTokenRatesOptions = {\n /** substrate chain connector of the host (only its `send` method is used, for one state_call per invocation) */\n connector: IChainConnectorDot\n /** the bittensor network id, defaults to \"bittensor\" (mainnet) */\n networkId?: NetworkId\n /** token list to price — non-dtao tokens (and dtao tokens with a coingeckoId) are ignored */\n tokens: TokenList\n /** freshly fetched rates list — dtao rates derive from the network's native TAO token rates in it */\n tokenRates: TokenRatesList\n /** previous rates list, used as keep-last fallback when fetching fails */\n previousRates: TokenRatesList\n /** custom fetch for the tao-data api (eg with auth headers), defaults to global fetch */\n customFetch?: typeof fetch\n /** tao-data api url, defaults to https://tda.talisman.xyz — pass null to skip the 24h-change fetch (eg testnets, which the api has no data for) */\n taoDataApiUrl?: string | null\n /** per-request timeout for the chain call and the tao-data api call, defaults to 10s */\n timeoutMs?: number\n}\n\n/**\n * Rates entries for the bittensor dtao (subnet alpha) tokens of `tokens`, priced from their\n * subnet pool combined with the TAO rates in `tokenRates`. On chain-fetch failure, falls\n * back to the token's entry in `previousRates` (keep-last). Never throws — a dtao pricing\n * failure must not discard the rates list it gets merged into.\n *\n * Hosts remain responsible for WHEN to call this (network enabled state, refresh cadence)\n * and for merging the result into their rates list/store.\n */\nexport const fetchDTaoTokenRates = async ({\n connector,\n networkId = DEFAULT_BITTENSOR_NETWORK_ID,\n tokens,\n tokenRates,\n previousRates,\n customFetch = fetch,\n taoDataApiUrl = DEFAULT_TAO_DATA_API_URL,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n}: FetchDTaoTokenRatesOptions): Promise<TokenRatesList> => {\n const dtaoTokens = Object.values(tokens).filter(\n (token): token is SubDTaoToken =>\n token.type === \"substrate-dtao\" && token.networkId === networkId && !token.coingeckoId\n )\n if (!dtaoTokens.length) return {}\n\n // alpha rates derive from the network's native TAO rates: without them there is nothing to\n // price against, and keep-last must not apply (stale entries would display as current)\n if (!tokenRates[subNativeTokenId(networkId)]) return {}\n\n // the two fetches are independent: run them concurrently\n const [prices, changes] = await Promise.all([\n Promise.race([\n fetchScaledAlphaPricesByNetuid(connector, networkId),\n throwAfter(timeoutMs, `Timed out after ${timeoutMs}ms`),\n ]).catch((err) => {\n log.warn(\"Failed to fetch alpha prices, keeping previous dtao rates\", err)\n return null\n }),\n taoDataApiUrl === null\n ? null\n : getAlphaPriceChangesByNetuid(taoDataApiUrl, customFetch, timeoutMs),\n ])\n\n if (!prices)\n return Object.fromEntries(\n dtaoTokens\n .filter((token) => previousRates[token.id])\n .map((token) => [token.id, previousRates[token.id]])\n )\n\n return Object.fromEntries(\n dtaoTokens\n .map((token) => {\n // never fabricate a zero price: a missing or zero pool price keeps the last rates\n const price = prices.get(token.netuid)\n const rates = price\n ? getDTaoTokenRates(token, tokenRates, price, changes?.get(token.netuid))\n : null\n return [token.id, rates ?? previousRates[token.id]] as const\n })\n .filter(([, rates]) => !!rates)\n )\n}\n","export const tryToDeleteOldTokenRatesDb = () => {\n try {\n // try and delete it if it's still there\n indexedDB.deleteDatabase(\"TalismanTokenRates\")\n } catch {\n // dont care if it fails\n }\n}\n","import { evmErc20TokenId, type Token, type TokenId } from \"@talismn/chaindata-provider\"\n\nimport {\n SUPPORTED_CURRENCIES,\n type TokenRateCurrency,\n type TokenRateData,\n type TokenRates,\n type TokenRatesList,\n} from \"./types\"\n\nexport class TokenRatesError extends Error {\n response?: Response\n constructor(message: string, response?: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport const ALL_CURRENCY_IDS = Object.keys(SUPPORTED_CURRENCIES) as TokenRateCurrency[]\nexport type CoinsApiConfig = {\n apiUrl: string\n}\n\nexport const DEFAULT_COINSAPI_CONFIG: CoinsApiConfig = {\n apiUrl: \"https://coins.talisman.xyz\",\n}\nexport async function fetchTokenRates(\n tokens: Record<TokenId, Token>,\n currencyIds: TokenRateCurrency[] = ALL_CURRENCY_IDS,\n config: CoinsApiConfig = DEFAULT_COINSAPI_CONFIG\n): Promise<TokenRatesList> {\n // create a map from `coingeckoId` -> `tokenId` for each token\n const coingeckoIdToTokenIds = Object.values(tokens)\n .flatMap((token) => {\n // BEGIN: LP tokens have a rate which is calculated later on, using the rates of two other tokens.\n //\n // This section contains the logic such that: if token is an LP token, then fetch the rates for the two underlying tokens.\n if (token.type === \"evm-uniswapv2\") {\n if (token.platform !== \"ethereum\") return []\n\n const getToken = (\n evmNetworkId: string,\n tokenAddress: `0x${string}`,\n coingeckoId: string\n ) => ({\n id: evmErc20TokenId(evmNetworkId, tokenAddress),\n coingeckoId,\n })\n\n const token0 = token.coingeckoId0\n ? [getToken(token.networkId, token.tokenAddress0, token.coingeckoId0)]\n : []\n const token1 = token.coingeckoId1\n ? [getToken(token.networkId, token.tokenAddress1, token.coingeckoId1)]\n : []\n\n return [...token0, ...token1]\n }\n // END: LP tokens have a rate which is calculated later on, using the rates of two other tokens.\n\n // ignore tokens which don't have a coingeckoId\n if (!token.coingeckoId) return []\n\n return [{ id: token.id, coingeckoId: token.coingeckoId }]\n })\n\n // get each token's coingeckoId\n .reduce(\n (coingeckoIdToTokenIds, { id, coingeckoId }) => {\n if (!coingeckoIdToTokenIds[coingeckoId]) coingeckoIdToTokenIds[coingeckoId] = []\n coingeckoIdToTokenIds[coingeckoId].push(id)\n return coingeckoIdToTokenIds\n },\n {} as Record<string, string[]>\n )\n\n // create a list of coingeckoIds we want to fetch\n const coingeckoIds = Object.keys(coingeckoIdToTokenIds).sort()\n\n // skip network request if there is nothing for us to fetch\n if (coingeckoIds.length < 1) return {}\n\n // If `currencyIds` includes `tao`, we need to always fetch the `bittensor` coingeckoId and the `usd` currency,\n // we can use these to calculate the currency rate for TAO relative to all other tokens.\n //\n // We support showing balances in TAO just like we support BTC/ETH/DOT, but coingecko doesn't support TAO as a vs currency rate.\n // We can macgyver our own TOKEN<>TAO rate by combining the TOKEN<>USD data with the TAO<>USD data.\n const hasVsTao = currencyIds.includes(\"tao\")\n const [effectiveCoingeckoIds, effectiveCurrencyIds] = hasVsTao\n ? [\n [...new Set(coingeckoIds).add(\"bittensor\")],\n [\n ...new Set(\n // don't request `tao` from coingecko (we calculate it from `usd`)\n currencyIds.filter((c) => c !== \"tao\")\n )\n // always include `usd` (so we can calculate `tao`)\n .add(\"usd\"),\n ],\n ]\n : [coingeckoIds, currencyIds]\n\n const response = await fetch(`${config.apiUrl}/token-rates`, {\n method: \"POST\",\n body: JSON.stringify({\n coingeckoIds: effectiveCoingeckoIds,\n currencyIds: effectiveCurrencyIds,\n }),\n })\n\n const rawTokenRates = (await response.json()) as RawTokenRates\n\n if (hasVsTao) {\n // calculate the TAO<>USD rate\n const effectiveTaoIndex = effectiveCoingeckoIds.indexOf(\"bittensor\")\n const effectiveUsdIndex = effectiveCurrencyIds.indexOf(\"usd\")\n const taoUsdRate = rawTokenRates[effectiveTaoIndex]?.[effectiveUsdIndex]?.[0]\n const taoUsdChange24h = rawTokenRates[effectiveTaoIndex]?.[effectiveUsdIndex]?.[2]\n\n // insert TOKEN<>TAO rate (calculated based on TAO<>USD rate and TOKEN<>USD rate) into each TOKEN\n const taoIndex = currencyIds.indexOf(\"tao\")\n rawTokenRates.forEach((rates, i) => {\n // hardcoded rate for TAO<>TAO\n if (i === effectiveTaoIndex) {\n rates?.splice(taoIndex, 0, [1, null, null])\n return\n }\n\n // get TOKEN<>USD rate\n const tokenUsdRate = rates?.[effectiveUsdIndex]?.[0]\n // calculate TOKEN<>TAO rate\n const tokenTaoRate =\n typeof tokenUsdRate === \"number\" && typeof taoUsdRate === \"number\" && taoUsdRate !== 0\n ? tokenUsdRate / taoUsdRate\n : null\n\n const tokenUsdChange24h = rates?.[effectiveUsdIndex]?.[2]\n const tokenTaoChange24h =\n typeof taoUsdChange24h === \"number\" && typeof tokenUsdChange24h === \"number\"\n ? (1 + tokenUsdChange24h) / (1 + taoUsdChange24h) - 1\n : null\n\n // insert at the correct location (based on the index of `tao` in `currencyIds`)\n rates?.splice(taoIndex, 0, [tokenTaoRate, null, tokenTaoChange24h])\n })\n }\n\n const tokenRates = parseTokenRatesFromApi(rawTokenRates, coingeckoIds, currencyIds)\n\n // build a TokenRatesList from the token prices result\n const ratesList: TokenRatesList = Object.fromEntries(\n Object.entries(tokens).map(([tokenId, token]) => [\n tokenId,\n token.coingeckoId ? (tokenRates[token.coingeckoId] ?? null) : null,\n ])\n ) as TokenRatesList\n\n return ratesList\n}\n\n// To save on bandwidth and work around response size limits, values are returned without json property names\n// (e.g. [[[12, 12332, 0.5]]] instead of { dot : {usd: { value: 12, marketCap: 12332, change24h: 0.5 }} })\ntype RawTokenRates = [number | null, number | null, number | null][][]\n\nconst parseTokenRatesFromApi = (\n rawTokenRates: RawTokenRates,\n coingeckoIds: string[],\n currencyIds: TokenRateCurrency[]\n): TokenRatesList => {\n return Object.fromEntries(\n coingeckoIds.map((coingeckoId, idx) => {\n const rates = rawTokenRates[idx]\n if (!rates) return [coingeckoId, null]\n\n return [\n coingeckoId,\n Object.fromEntries(\n currencyIds.map((currencyId, idx) => {\n const curRate = rates[idx]\n if (!curRate) return [currencyId, null]\n\n const [price, marketCap, change24h] = rates[idx]\n return [currencyId, { price, marketCap, change24h } as TokenRateData]\n })\n ) as TokenRates,\n ]\n })\n ) as TokenRatesList\n}\n"],"mappings":";;;;;;ACIA,IAAA,cAAe,UAAUA,sBAAgB;;;ACFzC,MAAa,uBAAuB;CAClC,KAAK;EAAE,MAAM;EAAW,QAAQ;CAAI;CACpC,KAAK;EAAE,MAAM;EAAY,QAAQ;CAAI;CACrC,KAAK;EAAE,MAAM;EAAY,QAAQ;CAAI;CACrC,KAAK;EAAE,MAAM;EAAa,QAAQ;CAAI;CAEtC,KAAK;EAAE,MAAM;EAAa,QAAQ;CAAI;CACtC,KAAK;EAAE,MAAM;EAAgB,QAAQ;CAAI;CACzC,KAAK;EAAE,MAAM;EAAQ,QAAQ;CAAI;CACjC,KAAK;EAAE,MAAM;EAAiB,QAAQ;CAAI;CAC1C,KAAK;EAAE,MAAM;EAAmB,QAAQ;CAAK;CAC7C,KAAK;EAAE,MAAM;EAAqB,QAAQ;CAAK;CAC/C,KAAK;EAAE,MAAM;EAAsB,QAAQ;CAAM;CACjD,KAAK;EAAE,MAAM;EAAgB,QAAQ;CAAI;CACzC,KAAK;EAAE,MAAM;EAAiB,QAAQ;CAAI;CAC1C,KAAK;EAAE,MAAM;EAAoB,QAAQ;CAAI;CAC7C,KAAK;EAAE,MAAM;EAAqB,QAAQ;CAAK;CAC/C,KAAK;EAAE,MAAM;EAAmB,QAAQ;CAAI;CAC5C,KAAK;EAAE,MAAM;EAAa,QAAQ;CAAI;CACtC,KAAK;EAAE,MAAM;EAAmB,QAAQ;CAAI;CAC5C,KAAK;EAAE,MAAM;EAAgB,QAAQ;CAAI;CACzC,KAAK;EAAE,MAAM;EAAgB,QAAQ;CAAI;CAEzC,KAAK;EAAE,MAAM;EAAoB,QAAQ;CAAK;AAEhD;AAUA,MAAa,uBAAmC;CAC9C,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CAEL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CAEL,KAAK;AAEP;;;;ACnDA,MAAM,2BAA2B,MAAM;;;;;AAMvC,MAAM,qBACJ,UACA,cACA,wBACuB;CACvB,IAAI,OAAO,wBAAwB,YAAY,CAAC,OAAO,SAAS,mBAAmB,GACjF,OAAO,KAAA;CAGT,IAAI,aAAa,OAAO,OAAO;CAE/B,IAAI,OAAO,iBAAiB,YAAY,CAAC,OAAO,SAAS,YAAY,GAAG,OAAO,KAAA;CAC/E,SAAS,IAAI,sBAAsB,QAAQ,IAAI,eAAe,OAAO,KAAK;AAC5E;;;;;;;;;;AAWA,MAAa,qBACX,OACA,YACA,kBACA,wBACG;CACH,IAAI;EAEF,MAAM,gBAAgB,WADH,iBAAiB,MAAM,SACA;EAC1C,IAAI,CAAC,eAAe,OAAO;EAG3B,IAAI,MAAM,WAAW,GAAG,OAAO,gBAAgB,aAAa;EAE5D,MAAM,aAAa,OAAO,OAAO,gBAAgB,CAAC,IAAI;EAEtD,MAAM,aAAa,cAAc;EACjC,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,aAAa,GAI5D,IAAI,CAAC,SACH,WAAW,YAAY;OAEvB,WAAW,YAAY;GACrB,OAAO,QAAQ,QAAQ;GACvB,WAAW,QAAQ,YAAY,QAAQ,YAAY,aAAa,KAAA;GAChE,WAAW,kBAAkB,UAAU,QAAQ,WAAW,mBAAmB;EAC/E;EAIJ,OAAO;CACT,SAAS,KAAK;EACZ,YAAI,MAAM,sCAAsC,GAAG;EACnD,OAAO;CACT;AACF;;;ACpEA,MAAM,+BAA+B;AACrC,MAAM,2BAA2B;AACjC,MAAM,qBAAqB;AAG3B,MAAM,mBAAmB,OAAO,OAAO;CAAE,QAAQ;CAAK,OAAO;AAAI,CAAC,CAAC;;AAGnE,MAAM,iCAAiC,OACrC,WACA,cACiC;CACjC,MAAM,SAAS,MAAM,UAAU,KAAa,WAAW,cAAc,CACnE,0CACA,IACF,CAAC;CACD,OAAO,IAAI,IAAI,iBAAiB,IAAI,MAAM,CAAC,CAAC,KAAK,EAAE,QAAQ,YAAY,CAAC,QAAQ,KAAK,CAAC,CAAC;AACzF;AAEA,MAAM,0BAA0B,IAAI;AACpC,MAAM,yCAAyB,IAAI,IAGjC;;;;;;AAOF,MAAM,+BAA+B,OACnC,QACA,aACA,cACwC;CACxC,MAAM,SAAS,uBAAuB,IAAI,MAAM;CAChD,IAAI,UAAU,YAAY,IAAI,IAAI,OAAO,YAAY,yBACnD,OAAO,OAAO;CAEhB,IAAI;EACF,MAAM,WAAW,MAAM,YAAY,GAAG,OAAO,SAAS,EACpD,QAAQ,YAAY,QAAQ,SAAS,EACvC,CAAC;EACD,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,OAAO,GAAG,SAAS,YAAY;EAC7E,MAAM,QAAS,MAAM,SAAS,KAAK;EAInC,MAAM,UAAU,IAAI,IAClB,MACG,KAAK,SAA2B,CAAC,KAAK,QAAQ,OAAO,KAAK,kBAAkB,CAAC,CAAC,CAAC,CAC/E,QAAQ,GAAG,YAAY,OAAO,SAAS,MAAM,CAAC,CACnD;EACA,uBAAuB,IAAI,QAAQ;GAAE,WAAW,YAAY,IAAI;GAAG;EAAQ,CAAC;EAC5E,OAAO;CACT,SAAS,KAAK;EACZ,YAAI,KAAK,2CAA2C,GAAG;EACvD,OAAO,QAAQ,WAAW;CAC5B;AACF;;;;;;;;;;AA8BA,MAAa,sBAAsB,OAAO,EACxC,WACA,YAAY,8BACZ,QACA,YACA,eACA,cAAc,OACd,gBAAgB,0BAChB,YAAY,yBAC6C;CACzD,MAAM,aAAa,OAAO,OAAO,MAAM,CAAC,CAAC,QACtC,UACC,MAAM,SAAS,oBAAoB,MAAM,cAAc,aAAa,CAAC,MAAM,WAC/E;CACA,IAAI,CAAC,WAAW,QAAQ,OAAO,CAAC;CAIhC,IAAI,CAAC,WAAW,iBAAiB,SAAS,IAAI,OAAO,CAAC;CAGtD,MAAM,CAAC,QAAQ,WAAW,MAAM,QAAQ,IAAI,CAC1C,QAAQ,KAAK,CACX,+BAA+B,WAAW,SAAS,GACnD,WAAW,WAAW,mBAAmB,UAAU,GAAG,CACxD,CAAC,CAAC,CAAC,OAAO,QAAQ;EAChB,YAAI,KAAK,6DAA6D,GAAG;EACzE,OAAO;CACT,CAAC,GACD,kBAAkB,OACd,OACA,6BAA6B,eAAe,aAAa,SAAS,CACxE,CAAC;CAED,IAAI,CAAC,QACH,OAAO,OAAO,YACZ,WACG,QAAQ,UAAU,cAAc,MAAM,GAAG,CAAC,CAC1C,KAAK,UAAU,CAAC,MAAM,IAAI,cAAc,MAAM,GAAG,CAAC,CACvD;CAEF,OAAO,OAAO,YACZ,WACG,KAAK,UAAU;EAEd,MAAM,QAAQ,OAAO,IAAI,MAAM,MAAM;EACrC,MAAM,QAAQ,QACV,kBAAkB,OAAO,YAAY,OAAO,SAAS,IAAI,MAAM,MAAM,CAAC,IACtE;EACJ,OAAO,CAAC,MAAM,IAAI,SAAS,cAAc,MAAM,GAAG;CACpD,CAAC,CAAC,CACD,QAAQ,GAAG,WAAW,CAAC,CAAC,KAAK,CAClC;AACF;;;ACxJA,MAAa,mCAAmC;CAC9C,IAAI;EAEF,UAAU,eAAe,oBAAoB;CAC/C,QAAQ,CAER;AACF;;;ACGA,IAAa,kBAAb,cAAqC,MAAM;CACzC;CACA,YAAY,SAAiB,UAAqB;EAChD,MAAM,OAAO;EACb,KAAK,WAAW;CAClB;AACF;AAEA,MAAa,mBAAmB,OAAO,KAAK,oBAAoB;AAKhE,MAAa,0BAA0C,EACrD,QAAQ,6BACV;AACA,eAAsB,gBACpB,QACA,cAAmC,kBACnC,SAAyB,yBACA;CAEzB,MAAM,wBAAwB,OAAO,OAAO,MAAM,CAAC,CAChD,SAAS,UAAU;EAIlB,IAAI,MAAM,SAAS,iBAAiB;GAClC,IAAI,MAAM,aAAa,YAAY,OAAO,CAAC;GAE3C,MAAM,YACJ,cACA,cACA,iBACI;IACJ,IAAI,gBAAgB,cAAc,YAAY;IAC9C;GACF;GAEA,MAAM,SAAS,MAAM,eACjB,CAAC,SAAS,MAAM,WAAW,MAAM,eAAe,MAAM,YAAY,CAAC,IACnE,CAAC;GACL,MAAM,SAAS,MAAM,eACjB,CAAC,SAAS,MAAM,WAAW,MAAM,eAAe,MAAM,YAAY,CAAC,IACnE,CAAC;GAEL,OAAO,CAAC,GAAG,QAAQ,GAAG,MAAM;EAC9B;EAIA,IAAI,CAAC,MAAM,aAAa,OAAO,CAAC;EAEhC,OAAO,CAAC;GAAE,IAAI,MAAM;GAAI,aAAa,MAAM;EAAY,CAAC;CAC1D,CAAC,CAAC,CAGD,QACE,uBAAuB,EAAE,IAAI,kBAAkB;EAC9C,IAAI,CAAC,sBAAsB,cAAc,sBAAsB,eAAe,CAAC;EAC/E,sBAAsB,YAAY,CAAC,KAAK,EAAE;EAC1C,OAAO;CACT,GACA,CAAC,CACH;CAGF,MAAM,eAAe,OAAO,KAAK,qBAAqB,CAAC,CAAC,KAAK;CAG7D,IAAI,aAAa,SAAS,GAAG,OAAO,CAAC;CAOrC,MAAM,WAAW,YAAY,SAAS,KAAK;CAC3C,MAAM,CAAC,uBAAuB,wBAAwB,WAClD,CACE,CAAC,GAAG,IAAI,IAAI,YAAY,CAAC,CAAC,IAAI,WAAW,CAAC,GAC1C,CACE,GAAG,IAAI,IAEL,YAAY,QAAQ,MAAM,MAAM,KAAK,CACvC,CAAC,CAEE,IAAI,KAAK,CACd,CACF,IACA,CAAC,cAAc,WAAW;CAU9B,MAAM,gBAAiB,OAAM,MARN,MAAM,GAAG,OAAO,OAAO,eAAe;EAC3D,QAAQ;EACR,MAAM,KAAK,UAAU;GACnB,cAAc;GACd,aAAa;EACf,CAAC;CACH,CAAC,EAAA,CAEqC,KAAK;CAE3C,IAAI,UAAU;EAEZ,MAAM,oBAAoB,sBAAsB,QAAQ,WAAW;EACnE,MAAM,oBAAoB,qBAAqB,QAAQ,KAAK;EAC5D,MAAM,aAAa,cAAc,kBAAkB,GAAG,kBAAkB,GAAG;EAC3E,MAAM,kBAAkB,cAAc,kBAAkB,GAAG,kBAAkB,GAAG;EAGhF,MAAM,WAAW,YAAY,QAAQ,KAAK;EAC1C,cAAc,SAAS,OAAO,MAAM;GAElC,IAAI,MAAM,mBAAmB;IAC3B,OAAO,OAAO,UAAU,GAAG;KAAC;KAAG;KAAM;IAAI,CAAC;IAC1C;GACF;GAGA,MAAM,eAAe,QAAQ,kBAAkB,GAAG;GAElD,MAAM,eACJ,OAAO,iBAAiB,YAAY,OAAO,eAAe,YAAY,eAAe,IACjF,eAAe,aACf;GAEN,MAAM,oBAAoB,QAAQ,kBAAkB,GAAG;GACvD,MAAM,oBACJ,OAAO,oBAAoB,YAAY,OAAO,sBAAsB,YAC/D,IAAI,sBAAsB,IAAI,mBAAmB,IAClD;GAGN,OAAO,OAAO,UAAU,GAAG;IAAC;IAAc;IAAM;GAAiB,CAAC;EACpE,CAAC;CACH;CAEA,MAAM,aAAa,uBAAuB,eAAe,cAAc,WAAW;CAUlF,OAPkC,OAAO,YACvC,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,SAAS,WAAW,CAC/C,SACA,MAAM,cAAe,WAAW,MAAM,gBAAgB,OAAQ,IAChE,CAAC,CAGY;AACjB;AAMA,MAAM,0BACJ,eACA,cACA,gBACmB;CACnB,OAAO,OAAO,YACZ,aAAa,KAAK,aAAa,QAAQ;EACrC,MAAM,QAAQ,cAAc;EAC5B,IAAI,CAAC,OAAO,OAAO,CAAC,aAAa,IAAI;EAErC,OAAO,CACL,aACA,OAAO,YACL,YAAY,KAAK,YAAY,QAAQ;GAEnC,IAAI,CADY,MAAM,MACR,OAAO,CAAC,YAAY,IAAI;GAEtC,MAAM,CAAC,OAAO,WAAW,aAAa,MAAM;GAC5C,OAAO,CAAC,YAAY;IAAE;IAAO;IAAW;GAAU,CAAkB;EACtE,CAAC,CACH,CACF;CACF,CAAC,CACH;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@talismn/token-rates",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"author": "Talisman",
|
|
5
5
|
"homepage": "https://talisman.xyz",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"anylogger": "^1.0.11",
|
|
25
25
|
"scale-ts": "^1.6.1",
|
|
26
|
-
"@talismn/chain-connectors": "1.0.
|
|
27
|
-
"@talismn/chaindata-provider": "2.0.
|
|
26
|
+
"@talismn/chain-connectors": "1.0.1",
|
|
27
|
+
"@talismn/chaindata-provider": "2.0.1",
|
|
28
28
|
"@talismn/util": "2.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|