From 6329c7e8c35bc03384dccfdd2bc69f13deac0665 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 11 Sep 2025 17:21:52 -0700 Subject: [PATCH 1/6] Pass the inverted platform mapping and platform priorites to cross chain mapping Combing the function means avoiding repeating a couple things, like inverting platforms and grabbing the platforms. There's a bug fixed as well where we were associating the first platform in an asset's platform list to the unique ID. It should be using the best platform as determined by the sorted platforms --- src/v3/providers/coingecko/coingecko.ts | 112 ++++++++---------------- 1 file changed, 36 insertions(+), 76 deletions(-) diff --git a/src/v3/providers/coingecko/coingecko.ts b/src/v3/providers/coingecko/coingecko.ts index 4968a37..94736b5 100644 --- a/src/v3/providers/coingecko/coingecko.ts +++ b/src/v3/providers/coingecko/coingecko.ts @@ -16,7 +16,6 @@ import { type RateEngine, type RateProvider, type TokenMap, - type TokenTypeMap, wasCrossChainDoc, wasExistingMappings } from '../../types' @@ -133,25 +132,40 @@ automatedTokenMappingsSyncDoc.onChange(automatedMappings => { } }) -const coingeckoToCrossChainMapping = async ( - coingeckoAssets: ReturnType, - tokenTypes: TokenTypeMap -): Promise => { +const tokenMapping: RateEngine = async () => { + const uidMapping: TokenMap = {} + const crossChainMapping: CrossChainMapping = {} + + // Add the mainnet currency mapping + for (const [key, value] of Object.entries(coingeckoMainnetCurrencyMapping)) { + if (value === null) continue + uidMapping[key] = { + id: value, + displayName: key + } + } + + const json = await fetchCoingecko( + `${config.providers.coingeckopro.uri}/api/v3/coins/list?include_platform=true` + ) + const tokenTypes = asCouchDoc(asTokenTypeMap)( + await dbSettings.get(TOKEN_TYPES_KEY) + ) + + const data = asCoingeckoAssetResponse(json) + const invertPlatformMapping: Record = {} for (const [key, value] of Object.entries(platformIdMappingSyncDoc.doc)) { if (value === null) continue invertPlatformMapping[value] = key } + const platformPriorityDoc = await dbSettings.get('platformPriority') const platformPriority = asCouchDoc(asNumberMap)(platformPriorityDoc).doc const getPriority = (k: string): number => platformPriority[k] ?? Number.MAX_SAFE_INTEGER - const out: CrossChainMapping = {} - - for (const asset of coingeckoAssets) { - let destAsset: { destChain: string; edgeTokenId: string } | undefined - + for (const asset of data) { const platforms = Object.entries(asset.platforms) const sortedPlatforms = platforms.sort( @@ -160,24 +174,32 @@ const coingeckoToCrossChainMapping = async ( getPriority(invertPlatformMapping[b[0]]) ) + let destAsset: { destChain: string; edgeTokenId: string } | undefined + for (const [platform, address] of sortedPlatforms) { const edgePluginId = invertPlatformMapping[platform] if (edgePluginId == null) continue - const tokenType = tokenTypes[edgePluginId] + const tokenType = tokenTypes.doc[edgePluginId] if (tokenType == null) continue try { const tokenId = createTokenId(tokenType, asset.symbol, address) if (tokenId == null) continue + // Build cross-chain mappings and track the best platform if (destAsset == null) { destAsset = { destChain: edgePluginId, edgeTokenId: tokenId } + // Create UID mapping for the best (first) platform + uidMapping[toCryptoKey({ pluginId: edgePluginId, tokenId })] = { + id: asset.id, + displayName: asset.name + } } else { - out[`${edgePluginId}_${tokenId}`] = { + crossChainMapping[`${edgePluginId}_${tokenId}`] = { sourceChain: edgePluginId, destChain: destAsset.destChain, currencyCode: asset.symbol, @@ -191,67 +213,9 @@ const coingeckoToCrossChainMapping = async ( } } - return out -} - -const tokenMapping: RateEngine = async () => { - const mapping: TokenMap = {} - - // Add the mainnet currency mapping - for (const [key, value] of Object.entries(coingeckoMainnetCurrencyMapping)) { - if (value === null) continue - mapping[key] = { - id: value, - displayName: key - } - } - - const json = await fetchCoingecko( - `${config.providers.coingeckopro.uri}/api/v3/coins/list?include_platform=true` - ) - const tokenTypes = asCouchDoc(asTokenTypeMap)( - await dbSettings.get(TOKEN_TYPES_KEY) - ) - - const data = asCoingeckoAssetResponse(json) - - const invertPlatformMapping: Record = {} - for (const [key, value] of Object.entries(platformIdMappingSyncDoc.doc)) { - if (value === null) continue - invertPlatformMapping[value] = key - } - - for (const asset of data) { - const firstPlatform: [string, string] | undefined = Object.entries( - asset.platforms - )[0] - if (firstPlatform == null) continue - - const [platform, contractAddress] = firstPlatform - - const pluginId = invertPlatformMapping[platform] - if (pluginId == null) continue - - try { - const tokenId = createTokenId( - tokenTypes.doc[pluginId], - asset.symbol, - contractAddress - ) - if (tokenId == null) continue - - mapping[toCryptoKey({ pluginId, tokenId })] = { - id: asset.id, - displayName: asset.name - } - } catch (e) { - // skip assets that we cannot create token id for - } - } - const combinedTokenMappings: TokenMap = { ...automatedTokenMappingsSyncDoc.doc, - ...mapping + ...uidMapping } await dbSettings.insert( @@ -264,15 +228,11 @@ const tokenMapping: RateEngine = async () => { const crossChainDocument = await dbSettings.get('crosschain:automated') const crossChainDoc = asCrossChainDoc(crossChainDocument) - const crossChainMappings = await coingeckoToCrossChainMapping( - data, - tokenTypes.doc as TokenTypeMap - ) await dbSettings.insert( wasCrossChainDoc({ id: crossChainDoc.id, rev: crossChainDoc.rev, - doc: crossChainMappings + doc: crossChainMapping }) ) } From 2e8fa1d776477c5c153d91d78c492629a5616bd7 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 11 Sep 2025 21:57:12 -0700 Subject: [PATCH 2/6] Loosen current response cleaner One rate missing could spoil an entire payload. A missing rate when the asset has an ID but no tracked volume --- src/v3/providers/coingecko/coingecko.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/v3/providers/coingecko/coingecko.ts b/src/v3/providers/coingecko/coingecko.ts index 94736b5..106619e 100644 --- a/src/v3/providers/coingecko/coingecko.ts +++ b/src/v3/providers/coingecko/coingecko.ts @@ -73,7 +73,7 @@ const asCoingeckoAssetResponse = asArray( const asGeckoBulkUsdResponse = asObject( asObject({ - usd: asNumber + usd: asMaybe(asNumber) }) ) @@ -247,7 +247,9 @@ const getCurrentRates = async (ids: Set): Promise => { ) const data = asGeckoBulkUsdResponse(json) for (const [key, value] of Object.entries(data)) { - out[key] = value.usd + if (value.usd != null) { + out[key] = value.usd + } } } catch (e) { console.error('coingecko current query error:', e) From cdf2754d0334118cc6ddedd018bfa544ad961cf3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 19 Sep 2025 12:09:22 -0700 Subject: [PATCH 3/6] Add hardcoded constant rates provider --- src/v3/providers/allProviders.ts | 2 ++ src/v3/providers/constantRates.ts | 58 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/v3/providers/constantRates.ts diff --git a/src/v3/providers/allProviders.ts b/src/v3/providers/allProviders.ts index 542cfab..46626aa 100644 --- a/src/v3/providers/allProviders.ts +++ b/src/v3/providers/allProviders.ts @@ -3,6 +3,7 @@ import { coingecko } from './coingecko/coingecko' import { coinmarketcap } from './coinmarketcap/coinmarketcap' // import { coinmonitor } from './coinmonitor' import { coinstore } from './coinstore' +import { constantRates } from './constantRates' import { couch } from './couch' import { currencyconverter } from './currencyconverter' import { edgerates } from './edgerates/edgerates' @@ -19,6 +20,7 @@ const looselyOrderedProviders: RateProvider[] = [ wazirx, coinmarketcap, coingecko, + constantRates, currencyconverter, couch, redis diff --git a/src/v3/providers/constantRates.ts b/src/v3/providers/constantRates.ts new file mode 100644 index 0000000..970a4f0 --- /dev/null +++ b/src/v3/providers/constantRates.ts @@ -0,0 +1,58 @@ +import { asNumber, asObject } from 'cleaners' +import { syncedDocument } from 'edge-server-tools' + +import type { NumberMap, RateBuckets, RateProvider } from '../types' +import { expandReturnedCryptoRates, reduceRequestedCryptoRates } from '../utils' +import { dbSettings } from './couch' + +const constantRateSyncDoc = syncedDocument('constantrates', asObject(asNumber)) + +constantRateSyncDoc.sync(dbSettings).catch(e => { + console.error('constantRateSyncDoc sync error', e) +}) + +export const constantRates: RateProvider = { + providerId: 'constantRates', + type: 'api', + getCryptoRates: async ({ targetFiat, requestedRates }, rightNow) => { + if (targetFiat !== 'USD') { + return { + foundRates: new Map(), + requestedRates + } + } + + const rateBuckets = reduceRequestedCryptoRates(requestedRates, rightNow) + + const allResults: RateBuckets = new Map() + rateBuckets.forEach((ids, date) => { + const dateResults: NumberMap = {} + ids.forEach(pluginIdTokenId => { + const rate = constantRateSyncDoc.doc[pluginIdTokenId] + if (rate != null) { + dateResults[pluginIdTokenId] = rate + } + }) + if (Object.keys(dateResults).length > 0) { + allResults.set(date, dateResults) + } + }) + + const out = expandReturnedCryptoRates(requestedRates, rightNow, allResults) + + return { + foundRates: out.foundRates, + requestedRates: out.requestedRates + } + }, + documents: [ + { + name: 'rates_settings', + templates: { + constantrates: {} + }, + syncedDocuments: [constantRateSyncDoc] + } + ], + engines: [] +} From 4f9361cdcbf4f73e3c7cf72768e35770c78758f6 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 19 Sep 2025 12:12:03 -0700 Subject: [PATCH 4/6] Add manual cross chain doc --- src/v3/providers/coingecko/coingecko.ts | 12 ++++--- src/v3/providers/edgerates/defaults.ts | 46 ++++++++++++++++++++++++- src/v3/providers/edgerates/edgerates.ts | 4 ++- src/v3/router.ts | 37 ++++++++++++++------ src/v3/types.ts | 2 +- 5 files changed, 83 insertions(+), 18 deletions(-) diff --git a/src/v3/providers/coingecko/coingecko.ts b/src/v3/providers/coingecko/coingecko.ts index 106619e..d9c8f66 100644 --- a/src/v3/providers/coingecko/coingecko.ts +++ b/src/v3/providers/coingecko/coingecko.ts @@ -226,13 +226,15 @@ const tokenMapping: RateEngine = async () => { }) ) - const crossChainDocument = await dbSettings.get('crosschain:automated') - const crossChainDoc = asCrossChainDoc(crossChainDocument) + const crossChainDefaultDocument = await dbSettings.get('crosschain') + const crossChainDefaultDoc = asCrossChainDoc(crossChainDefaultDocument) + const crossChainAutoDocument = await dbSettings.get('crosschain:automated') + const crossChainAutoDoc = asCrossChainDoc(crossChainAutoDocument) await dbSettings.insert( wasCrossChainDoc({ - id: crossChainDoc.id, - rev: crossChainDoc.rev, - doc: crossChainMapping + id: crossChainAutoDoc.id, + rev: crossChainAutoDoc.rev, + doc: { ...crossChainMapping, ...crossChainDefaultDoc.doc } }) ) } diff --git a/src/v3/providers/edgerates/defaults.ts b/src/v3/providers/edgerates/defaults.ts index 5c3ee3b..ad655d5 100644 --- a/src/v3/providers/edgerates/defaults.ts +++ b/src/v3/providers/edgerates/defaults.ts @@ -1,4 +1,4 @@ -import type { EdgeAsset, TokenTypeMap } from '../../types' +import type { CrossChainMapping, EdgeAsset, TokenTypeMap } from '../../types' export const defaultCrypto: EdgeAsset[] = [ { pluginId: 'bitcoin', tokenId: null }, @@ -554,4 +554,48 @@ export const defaultPlatformPriority: Record = { smartcash: 580, binance: 590, fantom: 600 + +export const defaultCrossChainMapping: CrossChainMapping = { + amoy: { + sourceChain: 'amoy', + destChain: 'polygon', + currencyCode: 'POL', + tokenId: null + }, + bitcointestnet: { + sourceChain: 'bitcointestnet', + destChain: 'bitcoin', + currencyCode: 'TESTBTC', + tokenId: null + }, + bitcointestnet4: { + sourceChain: 'bitcointestnet4', + destChain: 'bitcoin', + currencyCode: 'TESTBTC', + tokenId: null + }, + filecoinfevmcalibration: { + sourceChain: 'filecoinfevmcalibration', + destChain: 'filecoin', + currencyCode: 'tFIL', + tokenId: null + }, + sepolia: { + sourceChain: 'sepolia', + destChain: 'ethereum', + currencyCode: 'ETH', + tokenId: null + }, + thorchainrunestagenet: { + sourceChain: 'thorchainrunestagenet', + destChain: 'thorchainrune', + currencyCode: 'RUNE', + tokenId: null + }, + thorchainrunestagenet_tcy: { + sourceChain: 'thorchainrunestagenet', + destChain: 'thorchainrune', + currencyCode: 'TCY', + tokenId: 'tcy' + } } diff --git a/src/v3/providers/edgerates/edgerates.ts b/src/v3/providers/edgerates/edgerates.ts index 0b50694..2ba4cfb 100644 --- a/src/v3/providers/edgerates/edgerates.ts +++ b/src/v3/providers/edgerates/edgerates.ts @@ -14,6 +14,7 @@ import { fromCryptoKey } from '../../utils' import { dbSettings } from '../couch' import { client } from '../redis' import { + defaultCrossChainMapping, defaultCrypto, defaultFiat, defaultPlatformPriority, @@ -122,7 +123,8 @@ export const edgerates: RateProvider = { }, tokenTypes: defaultTokenTypes, platformPriority: defaultPlatformPriority, - 'crosschain:automated': {} + 'crosschain:automated': {}, + crosschain: defaultCrossChainMapping } } ], diff --git a/src/v3/router.ts b/src/v3/router.ts index 0e8a325..f3f59e9 100644 --- a/src/v3/router.ts +++ b/src/v3/router.ts @@ -59,9 +59,9 @@ const fixIncomingGetRatesParams = ( // Map incoming crypto assets to their cross-chain canonical versions // Also return a mapping from each original asset key to its canonical key +let crosschainMappings: CrossChainMapping = {} const applyCrossChainMappings = ( - params: GetRatesParams, - mapping: CrossChainMapping + params: GetRatesParams ): { mappedParams: GetRatesParams originalToCanonicalKey: Map @@ -69,7 +69,7 @@ const applyCrossChainMappings = ( const originalToCanonicalKey = new Map() const mappedCrypto = params.crypto.map(c => { const originalKey = toCryptoKey(c.asset) - const cross = mapping[originalKey] + const cross = crosschainMappings[originalKey] if (cross == null) { originalToCanonicalKey.set(originalKey, originalKey) return c @@ -91,12 +91,31 @@ const applyCrossChainMappings = ( } } -const crosschainMappings = syncedDocument( +const defaultCrossChainSyncDoc = syncedDocument( + 'crosschain', + asCrossChainMapping +) +const automatedCrossChainSyncDoc = syncedDocument( 'crosschain:automated', asCrossChainMapping ) -crosschainMappings.sync(dbSettings).catch(e => { - console.error('crosschainMappings sync error', e) +defaultCrossChainSyncDoc.sync(dbSettings).catch(e => { + console.error('defaultCrossChainSyncDoc sync error', e) +}) +automatedCrossChainSyncDoc.sync(dbSettings).catch(e => { + console.error('automatedCrossChainSyncDoc sync error', e) +}) +defaultCrossChainSyncDoc.onChange(defaultMappings => { + crosschainMappings = { + ...automatedCrossChainSyncDoc.doc, + ...defaultMappings + } +}) +automatedCrossChainSyncDoc.onChange(automatedMappings => { + crosschainMappings = { + ...automatedMappings, + ...defaultCrossChainSyncDoc.doc + } }) export const ratesV3 = async ( @@ -107,10 +126,8 @@ export const ratesV3 = async ( const params = fixIncomingGetRatesParams(request.req.body, rightNow) // Map all incoming crypto assets to their canonical versions - const { mappedParams, originalToCanonicalKey } = applyCrossChainMappings( - params, - crosschainMappings.doc - ) + const { mappedParams, originalToCanonicalKey } = + applyCrossChainMappings(params) const result = await getRates(mappedParams, rightNow) // Build a quick lookup from canonical key + isoDate -> rate diff --git a/src/v3/types.ts b/src/v3/types.ts index 8a87800..d93fa6f 100644 --- a/src/v3/types.ts +++ b/src/v3/types.ts @@ -178,7 +178,7 @@ export const asCrossChainMapping = asObject( sourceChain: asString, destChain: asString, currencyCode: asString, - tokenId: asString + tokenId: asEdgeTokenId }) ) export type CrossChainMapping = ReturnType From 206ccd367340df49348beb68d7422d1e1dbb066b Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 19 Sep 2025 12:12:48 -0700 Subject: [PATCH 5/6] Update UIDs --- src/v3/providers/coingecko/defaultPluginIdMapping.ts | 6 +++++- src/v3/providers/coinmarketcap/defaultPluginIdMapping.ts | 4 ++++ src/v3/providers/edgerates/defaults.ts | 9 ++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/v3/providers/coingecko/defaultPluginIdMapping.ts b/src/v3/providers/coingecko/defaultPluginIdMapping.ts index fe43c6a..66ceb54 100644 --- a/src/v3/providers/coingecko/defaultPluginIdMapping.ts +++ b/src/v3/providers/coingecko/defaultPluginIdMapping.ts @@ -1,6 +1,7 @@ import type { StringNullMap } from '../../types' export const coingeckoMainnetCurrencyMapping: StringNullMap = { + abstract: 'ethereum', algorand: 'algorand', arbitrum: 'ethereum', avalanche: 'avalanche-2', @@ -13,6 +14,7 @@ export const coingeckoMainnetCurrencyMapping: StringNullMap = { bitcoingold: 'bitcoin-gold', bitcoinsv: 'bitcoin-cash-sv', bobevm: 'ethereum', + botanix: 'bitcoin', cardano: 'cardano', celo: 'celo', coreum: 'coreum', @@ -59,7 +61,7 @@ export const coingeckoMainnetCurrencyMapping: StringNullMap = { ton: 'the-open-network', tron: 'tron', ufo: null, - vertcoin: null, + vertcoin: 'vertcoin', wax: null, zcash: 'zcash', zcoin: 'zcoin', @@ -68,6 +70,7 @@ export const coingeckoMainnetCurrencyMapping: StringNullMap = { } export const coingeckoPlatformIdMapping: StringNullMap = { + abstract: 'abstract', algorand: 'algorand', arbitrum: 'arbitrum-one', avalanche: 'avalanche', @@ -80,6 +83,7 @@ export const coingeckoPlatformIdMapping: StringNullMap = { bitcoingold: null, bitcoinsv: null, bobevm: 'bob-network', + botanix: 'botanix', cardano: 'cardano', celo: 'celo', coreum: 'coreum', diff --git a/src/v3/providers/coinmarketcap/defaultPluginIdMapping.ts b/src/v3/providers/coinmarketcap/defaultPluginIdMapping.ts index 8a5d291..a32effa 100644 --- a/src/v3/providers/coinmarketcap/defaultPluginIdMapping.ts +++ b/src/v3/providers/coinmarketcap/defaultPluginIdMapping.ts @@ -1,6 +1,7 @@ import type { StringNullMap } from '../../types' export const coinmarketcapMainnetCurrencyMapping: StringNullMap = { + abstract: '1027', algorand: '4030', arbitrum: '1027', avalanche: '5805', @@ -13,6 +14,7 @@ export const coinmarketcapMainnetCurrencyMapping: StringNullMap = { bitcoingold: '2083', bitcoinsv: '3602', bobevm: '1027', + botanix: '1', cardano: '2010', celo: '5567', coreum: '16399', @@ -68,6 +70,7 @@ export const coinmarketcapMainnetCurrencyMapping: StringNullMap = { } export const coinmarketcapPlatformIdMapping: StringNullMap = { + abstract: '247', algorand: '17', arbitrum: '51', avalanche: '28', @@ -80,6 +83,7 @@ export const coinmarketcapPlatformIdMapping: StringNullMap = { bitcoingold: null, bitcoinsv: null, bobevm: null, + botanix: null, cardano: '29', celo: '35', coreum: null, diff --git a/src/v3/providers/edgerates/defaults.ts b/src/v3/providers/edgerates/defaults.ts index ad655d5..22a2883 100644 --- a/src/v3/providers/edgerates/defaults.ts +++ b/src/v3/providers/edgerates/defaults.ts @@ -29,6 +29,8 @@ export const defaultCrypto: EdgeAsset[] = [ { pluginId: 'rsk', tokenId: null }, { pluginId: 'ethereum', tokenId: null }, { pluginId: 'ethereumclassic', tokenId: null }, + { pluginId: 'abstract', tokenId: null }, + { pluginId: 'botanix', tokenId: null }, { pluginId: 'ethereum', tokenId: '1985365e9f78359a9b6ad760e32412f4a445e862' }, { pluginId: 'ethereum', tokenId: '6b175474e89094c44da98b954eedeac495271d0f' }, { pluginId: 'ethereum', tokenId: '89d24a6b4ccb1b6faa2625fe562bdd9a23260359' }, @@ -427,6 +429,7 @@ export const defaultFiat: string[] = [ ] export const defaultTokenTypes: TokenTypeMap = { + abstract: 'evm', algorand: 'simple', arbitrum: 'evm', avalanche: 'evm', @@ -439,6 +442,7 @@ export const defaultTokenTypes: TokenTypeMap = { bitcoingold: null, bitcoinsv: null, bobevm: 'evm', + botanix: 'evm', cardano: null, celo: 'evm', coreum: 'cosmos', @@ -553,7 +557,10 @@ export const defaultPlatformPriority: Record = { pulsechain: 570, smartcash: 580, binance: 590, - fantom: 600 + fantom: 600, + abstract: 610, + botanix: 620 +} export const defaultCrossChainMapping: CrossChainMapping = { amoy: { From 136c3f571bfa2048e40ec83afb2023ec128ac887 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 19 Sep 2025 14:23:39 -0700 Subject: [PATCH 6/6] Call .sync on every synced document every 24 hours --- src/v3/providers/coingecko/coingecko.ts | 13 +++------- .../providers/coinmarketcap/coinmarketcap.ts | 13 +++------- src/v3/providers/constantRates.ts | 11 ++++---- src/v3/router.ts | 10 +++----- src/v3/utils.ts | 25 +++++++++++++++++++ 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/v3/providers/coingecko/coingecko.ts b/src/v3/providers/coingecko/coingecko.ts index d9c8f66..71c3abb 100644 --- a/src/v3/providers/coingecko/coingecko.ts +++ b/src/v3/providers/coingecko/coingecko.ts @@ -20,6 +20,7 @@ import { wasExistingMappings } from '../../types' import { + create30MinuteSyncInterval, createTokenId, expandReturnedCryptoRates, isCurrent, @@ -110,15 +111,9 @@ const platformIdMappingSyncDoc = syncedDocument( 'coingecko:platforms', asStringNullMap ) -manualTokenMappingsSyncDoc.sync(dbSettings).catch(e => { - console.error('manualTokenMappingsSyncDoc sync error', e) -}) -automatedTokenMappingsSyncDoc.sync(dbSettings).catch(e => { - console.error('automatedTokenMappingsSyncDoc sync error', e) -}) -platformIdMappingSyncDoc.sync(dbSettings).catch(e => { - console.error('platformIdMappingSyncDoc sync error', e) -}) +create30MinuteSyncInterval(manualTokenMappingsSyncDoc, dbSettings) +create30MinuteSyncInterval(automatedTokenMappingsSyncDoc, dbSettings) +create30MinuteSyncInterval(platformIdMappingSyncDoc, dbSettings) manualTokenMappingsSyncDoc.onChange(manualMappings => { coingeckoTokenIdMap = { ...automatedTokenMappingsSyncDoc.doc, diff --git a/src/v3/providers/coinmarketcap/coinmarketcap.ts b/src/v3/providers/coinmarketcap/coinmarketcap.ts index 74e6775..5078806 100644 --- a/src/v3/providers/coinmarketcap/coinmarketcap.ts +++ b/src/v3/providers/coinmarketcap/coinmarketcap.ts @@ -22,6 +22,7 @@ import { wasExistingMappings } from '../../types' import { + create30MinuteSyncInterval, createTokenId, expandReturnedCryptoRates, isCurrent, @@ -144,15 +145,9 @@ const platformIdMappingSyncDoc = syncedDocument( 'coinmarketcap:platforms', asStringNullMap ) -userTokenMappingsSyncDoc.sync(dbSettings).catch(e => { - console.error('manualTokenMappingsSyncDoc sync error', e) -}) -automatedTokenMappingsSyncDoc.sync(dbSettings).catch(e => { - console.error('automatedTokenMappingsSyncDoc sync error', e) -}) -platformIdMappingSyncDoc.sync(dbSettings).catch(e => { - console.error('platformIdMappingSyncDoc sync error', e) -}) +create30MinuteSyncInterval(userTokenMappingsSyncDoc, dbSettings) +create30MinuteSyncInterval(automatedTokenMappingsSyncDoc, dbSettings) +create30MinuteSyncInterval(platformIdMappingSyncDoc, dbSettings) userTokenMappingsSyncDoc.onChange(userMappings => { coinmarketcapTokenIdMap = { ...automatedTokenMappingsSyncDoc.doc, diff --git a/src/v3/providers/constantRates.ts b/src/v3/providers/constantRates.ts index 970a4f0..6dffb02 100644 --- a/src/v3/providers/constantRates.ts +++ b/src/v3/providers/constantRates.ts @@ -2,14 +2,15 @@ import { asNumber, asObject } from 'cleaners' import { syncedDocument } from 'edge-server-tools' import type { NumberMap, RateBuckets, RateProvider } from '../types' -import { expandReturnedCryptoRates, reduceRequestedCryptoRates } from '../utils' +import { + create30MinuteSyncInterval, + expandReturnedCryptoRates, + reduceRequestedCryptoRates +} from '../utils' import { dbSettings } from './couch' const constantRateSyncDoc = syncedDocument('constantrates', asObject(asNumber)) - -constantRateSyncDoc.sync(dbSettings).catch(e => { - console.error('constantRateSyncDoc sync error', e) -}) +create30MinuteSyncInterval(constantRateSyncDoc, dbSettings) export const constantRates: RateProvider = { providerId: 'constantRates', diff --git a/src/v3/router.ts b/src/v3/router.ts index f3f59e9..188e73c 100644 --- a/src/v3/router.ts +++ b/src/v3/router.ts @@ -15,7 +15,7 @@ import { type GetRatesParams, type IncomingGetRatesParams } from './types' -import { toCryptoKey } from './utils' +import { create30MinuteSyncInterval, toCryptoKey } from './utils' const fixIncomingGetRatesParams = ( rawParams: IncomingGetRatesParams, @@ -99,12 +99,8 @@ const automatedCrossChainSyncDoc = syncedDocument( 'crosschain:automated', asCrossChainMapping ) -defaultCrossChainSyncDoc.sync(dbSettings).catch(e => { - console.error('defaultCrossChainSyncDoc sync error', e) -}) -automatedCrossChainSyncDoc.sync(dbSettings).catch(e => { - console.error('automatedCrossChainSyncDoc sync error', e) -}) +create30MinuteSyncInterval(defaultCrossChainSyncDoc, dbSettings) +create30MinuteSyncInterval(automatedCrossChainSyncDoc, dbSettings) defaultCrossChainSyncDoc.onChange(defaultMappings => { crosschainMappings = { ...automatedCrossChainSyncDoc.doc, diff --git a/src/v3/utils.ts b/src/v3/utils.ts index b68de9f..1742b65 100644 --- a/src/v3/utils.ts +++ b/src/v3/utils.ts @@ -1,3 +1,6 @@ +import type { SyncedDocument } from 'edge-server-tools' +import type nano from 'nano' + import { FIVE_MINUTES, ONE_MINUTE, TWENTY_FOUR_HOURS } from './constants' import type { CryptoRateMap, @@ -304,3 +307,25 @@ export const isCurrent = ( } export const isCurrentFiat = (isoDate: Date, rightNow: Date): boolean => isCurrent(isoDate, rightNow, TWENTY_FOUR_HOURS) + +/** + * Create an interval to manually refresh the synced document. + * This is a workaround in case we lose the connection to the CouchDB changes feed. + */ +export const create30MinuteSyncInterval = ( + syncedDocument: SyncedDocument, + db: nano.DocumentScope +): void => { + syncedDocument + .sync(db) + .then(() => { + setInterval(() => { + syncedDocument.sync(db).catch(e => { + console.error('interval sync error', syncedDocument.id, e) + }) + }, 30 * ONE_MINUTE) + }) + .catch(e => { + console.error('create30MinuteSyncInterval error', syncedDocument.id, e) + }) +}