@pioneer-platform/markets 8.11.28 → 8.13.0
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/CHANGELOG.md +12 -0
- package/METRICS_MONITORING.md +285 -0
- package/PRICING_IMPROVEMENTS.md +327 -0
- package/lib/api-metrics-reporter.d.ts +20 -0
- package/lib/api-metrics-reporter.js +356 -0
- package/lib/ccxt-pricing.d.ts +34 -0
- package/lib/ccxt-pricing.js +421 -0
- package/lib/index.js +784 -145
- package/lib/metrics-logger.d.ts +72 -0
- package/lib/metrics-logger.js +435 -0
- package/lib/refill-scheduler.d.ts +22 -0
- package/lib/refill-scheduler.js +226 -0
- package/lib/token-bucket-manager.d.ts +47 -0
- package/lib/token-bucket-manager.js +342 -0
- package/lib/token-bucket.d.ts +87 -0
- package/lib/token-bucket.js +341 -0
- package/package.json +7 -4
- package/.turbo/turbo-build.log +0 -2
- package/test-results.log +0 -5073
package/lib/index.js
CHANGED
|
@@ -58,6 +58,11 @@ var TAG = " | market-module | ";
|
|
|
58
58
|
var pioneer_discovery_1 = require("@pioneer-platform/pioneer-discovery");
|
|
59
59
|
var caip_1 = require("@shapeshiftoss/caip");
|
|
60
60
|
var Bottleneck = require('bottleneck');
|
|
61
|
+
var token_bucket_manager_1 = require("./token-bucket-manager");
|
|
62
|
+
var metrics_logger_1 = require("./metrics-logger");
|
|
63
|
+
var refill_scheduler_1 = require("./refill-scheduler");
|
|
64
|
+
var ccxt_pricing_1 = require("./ccxt-pricing");
|
|
65
|
+
var api_metrics_reporter_1 = require("./api-metrics-reporter");
|
|
61
66
|
var axiosLib = require('axios');
|
|
62
67
|
var Axios = axiosLib.default || axiosLib;
|
|
63
68
|
var https = require('https');
|
|
@@ -121,15 +126,44 @@ var unpriceableTokensCache = new Map();
|
|
|
121
126
|
var UNPRICEABLE_TOKEN_PREFIX = 'unpriceable_token:';
|
|
122
127
|
var UNPRICEABLE_TOKEN_TTL = 7 * 24 * 60 * 60; // 7 days
|
|
123
128
|
var UNPRICEABLE_TOKEN_TTL_MS = UNPRICEABLE_TOKEN_TTL * 1000;
|
|
129
|
+
// Redis keys for monitoring
|
|
130
|
+
var REDIS_UNPRICEABLE_SET = 'markets:unpriceable_tokens';
|
|
131
|
+
var REDIS_API_METRICS_KEY = 'markets:api_metrics';
|
|
132
|
+
// CRITICAL: Major cryptocurrencies that should NEVER be marked as unpriceable
|
|
133
|
+
// Even if all APIs fail, these are known legitimate assets - API failures are temporary
|
|
134
|
+
var MAJOR_CRYPTO_WHITELIST = new Set([
|
|
135
|
+
'bip122:000000000019d6689c085ae165831e93/slip44:0', // Bitcoin
|
|
136
|
+
'eip155:1/slip44:60', // Ethereum
|
|
137
|
+
'eip155:56/slip44:60', // BNB Chain
|
|
138
|
+
'eip155:137/slip44:60', // Polygon
|
|
139
|
+
'eip155:42161/slip44:60', // Arbitrum
|
|
140
|
+
'eip155:10/slip44:60', // Optimism
|
|
141
|
+
'eip155:8453/slip44:60', // Base
|
|
142
|
+
'cosmos:cosmoshub-4/slip44:118', // Cosmos
|
|
143
|
+
'cosmos:osmosis-1/slip44:118', // Osmosis
|
|
144
|
+
'cosmos:thorchain-mainnet-v1/slip44:931', // Thorchain
|
|
145
|
+
'cosmos:mayachain-mainnet-v1/slip44:931', // Mayachain
|
|
146
|
+
'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144', // XRP
|
|
147
|
+
'bip122:12a765e31ffd4059bada1e25190f6e98/slip44:2', // Litecoin
|
|
148
|
+
'bip122:00000000001a91e3dace36e2be3bf030/slip44:3', // Dogecoin
|
|
149
|
+
'bip122:000007d91d1254d60e2dd1ae58038307/slip44:5', // Dash
|
|
150
|
+
'bip122:000000000000000000651ef99cb9fcbe/slip44:145', // Bitcoin Cash
|
|
151
|
+
]);
|
|
124
152
|
// NOTE: Rate limiting is now enforced at the worker level (pioneer-server)
|
|
125
153
|
// using a Redis mutex to ensure ONLY ONE market API call at a time across all processes
|
|
126
154
|
// The Bottleneck limiters below are kept as a safety net for direct module usage
|
|
155
|
+
// Token bucket and metrics system
|
|
156
|
+
var tokenBucketManager = null;
|
|
157
|
+
var metricsLogger = null;
|
|
158
|
+
var isInitializing = false;
|
|
127
159
|
module.exports = {
|
|
128
160
|
init: function (settings) {
|
|
129
161
|
if (settings === null || settings === void 0 ? void 0 : settings.apiKey) {
|
|
130
162
|
COINGECKO_API_KEY = settings.apiKey;
|
|
131
163
|
}
|
|
132
164
|
//if(!COINGECKO_API_KEY) throw Error("api key required! set env COINGECKO_API_KEY")
|
|
165
|
+
// Start hourly metrics reporting to Discord
|
|
166
|
+
(0, api_metrics_reporter_1.startHourlyReporting)();
|
|
133
167
|
},
|
|
134
168
|
// NEW: CAIP-first individual asset price lookup
|
|
135
169
|
getAssetPriceByCaip: function (caip, returnSource) {
|
|
@@ -139,6 +173,26 @@ module.exports = {
|
|
|
139
173
|
getBatchPricesByCaip: function (caips, returnSource) {
|
|
140
174
|
return get_batch_prices_by_caip(caips, returnSource);
|
|
141
175
|
},
|
|
176
|
+
// NEW: CCXT direct access (for testing/debugging)
|
|
177
|
+
getPriceFromCCXT: function (symbol) {
|
|
178
|
+
return (0, ccxt_pricing_1.getPriceFromCCXT)(symbol);
|
|
179
|
+
},
|
|
180
|
+
getBatchPricesFromCCXT: function (symbols) {
|
|
181
|
+
return (0, ccxt_pricing_1.getBatchPricesFromCCXT)(symbols);
|
|
182
|
+
},
|
|
183
|
+
// NEW: Metrics and monitoring
|
|
184
|
+
getCurrentMetrics: function () {
|
|
185
|
+
return (0, api_metrics_reporter_1.getCurrentMetrics)();
|
|
186
|
+
},
|
|
187
|
+
getUnpriceableTokens: function (limit) {
|
|
188
|
+
return (0, api_metrics_reporter_1.getUnpriceableTokens)(limit);
|
|
189
|
+
},
|
|
190
|
+
clearUnpriceableToken: function (caip) {
|
|
191
|
+
return (0, api_metrics_reporter_1.clearUnpriceableToken)(caip);
|
|
192
|
+
},
|
|
193
|
+
sendMetricsReport: function () {
|
|
194
|
+
return (0, api_metrics_reporter_1.sendHourlyMetricsReport)();
|
|
195
|
+
},
|
|
142
196
|
// Legacy bulk fetch functions
|
|
143
197
|
getAssetsCoinCap: function () {
|
|
144
198
|
return get_assets_coincap();
|
|
@@ -163,6 +217,16 @@ module.exports = {
|
|
|
163
217
|
},
|
|
164
218
|
buildBalances: function (marketInfoCoinCap, marketInfoCoinGecko, pubkeys, context) {
|
|
165
219
|
return build_balances(marketInfoCoinCap, marketInfoCoinGecko, pubkeys);
|
|
220
|
+
},
|
|
221
|
+
// NEW: Token bucket management
|
|
222
|
+
getTokenBucketManager: function () {
|
|
223
|
+
return tokenBucketManager;
|
|
224
|
+
},
|
|
225
|
+
getMetricsLogger: function () {
|
|
226
|
+
return metricsLogger;
|
|
227
|
+
},
|
|
228
|
+
initializeTokenBuckets: function () {
|
|
229
|
+
return initializeTokenBuckets();
|
|
166
230
|
}
|
|
167
231
|
};
|
|
168
232
|
var update_cache = function () {
|
|
@@ -736,54 +800,151 @@ var is_unpriceable_token = function (caip) {
|
|
|
736
800
|
* Cache token as unpriceable (not found in any of our 3 paid APIs)
|
|
737
801
|
* Stores in-memory for instant lookups + Redis for persistence
|
|
738
802
|
* This prevents wasting API calls on scam/spam tokens
|
|
803
|
+
*
|
|
804
|
+
* CRITICAL PROTECTION: Major cryptocurrencies are NEVER marked as unpriceable
|
|
805
|
+
* API failures for BTC, ETH, etc. are temporary - we retry instead of caching failure
|
|
739
806
|
*/
|
|
740
807
|
var cache_unpriceable_token = function (caip) {
|
|
741
808
|
return __awaiter(this, void 0, void 0, function () {
|
|
742
|
-
var tag, key;
|
|
809
|
+
var tag, error_1, key;
|
|
743
810
|
return __generator(this, function (_a) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
811
|
+
switch (_a.label) {
|
|
812
|
+
case 0:
|
|
813
|
+
tag = TAG + ' | cache_unpriceable_token | ';
|
|
814
|
+
// CRITICAL: Never mark major cryptocurrencies as unpriceable!
|
|
815
|
+
// If all APIs fail for Bitcoin/Ethereum/etc., it's an API issue, not a scam token
|
|
816
|
+
if (MAJOR_CRYPTO_WHITELIST.has(caip)) {
|
|
817
|
+
log.error(tag, "\uD83D\uDEA8 BLOCKED: Attempted to mark major cryptocurrency as unpriceable: ".concat(caip));
|
|
818
|
+
log.error(tag, "This indicates all 3 price APIs failed - likely rate limiting or API outage");
|
|
819
|
+
log.error(tag, "NOT caching as unpriceable - will retry on next request");
|
|
820
|
+
return [2 /*return*/]; // Do NOT cache - allow retries for major coins
|
|
821
|
+
}
|
|
822
|
+
// Store in-memory for instant lookups
|
|
823
|
+
unpriceableTokensCache.set(caip, {
|
|
824
|
+
markedAt: Date.now(),
|
|
825
|
+
maybeScam: true
|
|
826
|
+
});
|
|
827
|
+
_a.label = 1;
|
|
828
|
+
case 1:
|
|
829
|
+
_a.trys.push([1, 3, , 4]);
|
|
830
|
+
return [4 /*yield*/, redis.sadd(REDIS_UNPRICEABLE_SET, caip)];
|
|
831
|
+
case 2:
|
|
832
|
+
_a.sent();
|
|
833
|
+
log.debug(tag, "Added ".concat(caip, " to unpriceable tokens set"));
|
|
834
|
+
return [3 /*break*/, 4];
|
|
835
|
+
case 3:
|
|
836
|
+
error_1 = _a.sent();
|
|
837
|
+
log.debug(tag, "Error adding to unpriceable set: ".concat(error_1));
|
|
838
|
+
return [3 /*break*/, 4];
|
|
839
|
+
case 4:
|
|
840
|
+
// Also persist to Redis for cross-process sharing (but don't wait/block)
|
|
841
|
+
try {
|
|
842
|
+
key = UNPRICEABLE_TOKEN_PREFIX + caip;
|
|
843
|
+
redis.setex(key, UNPRICEABLE_TOKEN_TTL, JSON.stringify({
|
|
844
|
+
caip: caip,
|
|
845
|
+
markedAt: new Date().toISOString(),
|
|
846
|
+
reason: 'not_found_in_paid_apis',
|
|
847
|
+
maybeScam: true
|
|
848
|
+
})).catch(function (err) {
|
|
849
|
+
// Ignore Redis errors - in-memory cache is primary
|
|
850
|
+
log.debug(tag, "Redis error (non-blocking): ".concat(err.message));
|
|
851
|
+
});
|
|
852
|
+
log.info(tag, "\u2705 Cached token as unpriceable for 7 days: ".concat(caip));
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
// If Redis fails, don't block - in-memory cache is already set
|
|
856
|
+
log.debug(tag, "Redis error caching unpriceable token (non-critical): ".concat(error));
|
|
857
|
+
}
|
|
858
|
+
return [2 /*return*/];
|
|
763
859
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
};
|
|
863
|
+
/**
|
|
864
|
+
* Track API success/failure for monitoring
|
|
865
|
+
*/
|
|
866
|
+
var trackAPIMetric = function (api, success, error) {
|
|
867
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
868
|
+
var tag, metricsData, metrics, apiKey, error_2;
|
|
869
|
+
return __generator(this, function (_a) {
|
|
870
|
+
switch (_a.label) {
|
|
871
|
+
case 0:
|
|
872
|
+
tag = TAG + ' | trackAPIMetric | ';
|
|
873
|
+
_a.label = 1;
|
|
874
|
+
case 1:
|
|
875
|
+
_a.trys.push([1, 4, , 5]);
|
|
876
|
+
return [4 /*yield*/, redis.get(REDIS_API_METRICS_KEY)];
|
|
877
|
+
case 2:
|
|
878
|
+
metricsData = _a.sent();
|
|
879
|
+
metrics = metricsData ? JSON.parse(metricsData) : {
|
|
880
|
+
coingecko: { success: 0, failures: 0 },
|
|
881
|
+
coinmarketcap: { success: 0, failures: 0 },
|
|
882
|
+
coincap: { success: 0, failures: 0 },
|
|
883
|
+
ccxt: { success: 0, failures: 0 },
|
|
884
|
+
timestamp: Date.now()
|
|
885
|
+
};
|
|
886
|
+
apiKey = api.toLowerCase().replace(/[^a-z]/g, '');
|
|
887
|
+
if (metrics[apiKey]) {
|
|
888
|
+
if (success) {
|
|
889
|
+
metrics[apiKey].success++;
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
metrics[apiKey].failures++;
|
|
893
|
+
if (error) {
|
|
894
|
+
metrics[apiKey].lastError = error;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
// Save back to Redis (expires after 2 hours)
|
|
899
|
+
return [4 /*yield*/, redis.setex(REDIS_API_METRICS_KEY, 7200, JSON.stringify(metrics))];
|
|
900
|
+
case 3:
|
|
901
|
+
// Save back to Redis (expires after 2 hours)
|
|
902
|
+
_a.sent();
|
|
903
|
+
return [3 /*break*/, 5];
|
|
904
|
+
case 4:
|
|
905
|
+
error_2 = _a.sent();
|
|
906
|
+
log.debug(tag, "Error tracking API metric: ".concat(error_2));
|
|
907
|
+
return [3 /*break*/, 5];
|
|
908
|
+
case 5: return [2 /*return*/];
|
|
767
909
|
}
|
|
768
|
-
return [2 /*return*/];
|
|
769
910
|
});
|
|
770
911
|
});
|
|
771
912
|
};
|
|
772
913
|
/**
|
|
773
914
|
* Get price from CoinGecko by coin ID
|
|
774
|
-
* Now with rate limiting
|
|
915
|
+
* Now with token bucket rate limiting and metrics logging
|
|
775
916
|
*/
|
|
776
917
|
var get_price_from_coingecko = function (coingeckoId) {
|
|
777
918
|
return __awaiter(this, void 0, void 0, function () {
|
|
778
|
-
var tag, url, headers, response, price,
|
|
919
|
+
var tag, startTime, hasTokens, url, headers, response, responseTime, price, error_3, responseTime, status_3, wasRateLimited, discordNotifier, importError_1, alertError_1;
|
|
779
920
|
var _a;
|
|
780
921
|
return __generator(this, function (_b) {
|
|
781
922
|
switch (_b.label) {
|
|
782
923
|
case 0:
|
|
783
924
|
tag = TAG + ' | get_price_from_coingecko | ';
|
|
784
|
-
|
|
925
|
+
startTime = Date.now();
|
|
926
|
+
if (!(tokenBucketManager && tokenBucketManager.isInitialized())) return [3 /*break*/, 4];
|
|
927
|
+
return [4 /*yield*/, tokenBucketManager.tryConsume('coingecko', 1)];
|
|
785
928
|
case 1:
|
|
786
|
-
_b.
|
|
929
|
+
hasTokens = _b.sent();
|
|
930
|
+
if (!!hasTokens) return [3 /*break*/, 4];
|
|
931
|
+
log.warn(tag, '🚫 CoinGecko token bucket exhausted! Skipping API call.');
|
|
932
|
+
if (!metricsLogger) return [3 /*break*/, 3];
|
|
933
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
934
|
+
apiName: 'coingecko',
|
|
935
|
+
endpoint: '/simple/price',
|
|
936
|
+
success: false,
|
|
937
|
+
wasThrottled: true,
|
|
938
|
+
error: 'Token bucket exhausted',
|
|
939
|
+
tokensUsed: 0,
|
|
940
|
+
timestamp: new Date()
|
|
941
|
+
})];
|
|
942
|
+
case 2:
|
|
943
|
+
_b.sent();
|
|
944
|
+
_b.label = 3;
|
|
945
|
+
case 3: return [2 /*return*/, 0]; // Don't call API if no tokens!
|
|
946
|
+
case 4:
|
|
947
|
+
_b.trys.push([4, 11, , 24]);
|
|
787
948
|
url = "".concat(URL_COINGECKO, "simple/price?ids=").concat(coingeckoId, "&vs_currencies=usd");
|
|
788
949
|
log.debug(tag, "Fetching from CoinGecko: ".concat(url));
|
|
789
950
|
headers = {};
|
|
@@ -791,26 +952,101 @@ var get_price_from_coingecko = function (coingeckoId) {
|
|
|
791
952
|
headers['x-cg-pro-api-key'] = COINGECKO_API_KEY;
|
|
792
953
|
}
|
|
793
954
|
return [4 /*yield*/, axios.get(url, { headers: headers })];
|
|
794
|
-
case
|
|
955
|
+
case 5:
|
|
795
956
|
response = _b.sent();
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
957
|
+
responseTime = Date.now() - startTime;
|
|
958
|
+
if (!(response.data && response.data[coingeckoId] && response.data[coingeckoId].usd)) return [3 /*break*/, 8];
|
|
959
|
+
price = parseFloat(response.data[coingeckoId].usd);
|
|
960
|
+
log.debug(tag, "\u2705 CoinGecko price for ".concat(coingeckoId, ": $").concat(price));
|
|
961
|
+
if (!metricsLogger) return [3 /*break*/, 7];
|
|
962
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
963
|
+
apiName: 'coingecko',
|
|
964
|
+
endpoint: '/simple/price',
|
|
965
|
+
success: true,
|
|
966
|
+
responseTime: responseTime,
|
|
967
|
+
price: price,
|
|
968
|
+
tokensUsed: 1,
|
|
969
|
+
costUSD: 0.02,
|
|
970
|
+
timestamp: new Date()
|
|
971
|
+
})];
|
|
972
|
+
case 6:
|
|
973
|
+
_b.sent();
|
|
974
|
+
_b.label = 7;
|
|
975
|
+
case 7: return [2 /*return*/, price];
|
|
976
|
+
case 8:
|
|
801
977
|
log.debug(tag, "No price data from CoinGecko for ".concat(coingeckoId));
|
|
802
|
-
return [
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
case
|
|
978
|
+
if (!metricsLogger) return [3 /*break*/, 10];
|
|
979
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
980
|
+
apiName: 'coingecko',
|
|
981
|
+
endpoint: '/simple/price',
|
|
982
|
+
success: false,
|
|
983
|
+
responseTime: responseTime,
|
|
984
|
+
error: 'No price data',
|
|
985
|
+
tokensUsed: 1,
|
|
986
|
+
costUSD: 0.02,
|
|
987
|
+
timestamp: new Date()
|
|
988
|
+
})];
|
|
989
|
+
case 9:
|
|
990
|
+
_b.sent();
|
|
991
|
+
_b.label = 10;
|
|
992
|
+
case 10: return [2 /*return*/, 0];
|
|
993
|
+
case 11:
|
|
994
|
+
error_3 = _b.sent();
|
|
995
|
+
responseTime = Date.now() - startTime;
|
|
996
|
+
status_3 = ((_a = error_3.response) === null || _a === void 0 ? void 0 : _a.status) || error_3.status;
|
|
997
|
+
wasRateLimited = status_3 === 429 || status_3 === 403;
|
|
998
|
+
if (!metricsLogger) return [3 /*break*/, 13];
|
|
999
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1000
|
+
apiName: 'coingecko',
|
|
1001
|
+
endpoint: '/simple/price',
|
|
1002
|
+
success: false,
|
|
1003
|
+
responseTime: responseTime,
|
|
1004
|
+
statusCode: status_3,
|
|
1005
|
+
error: error_3.message,
|
|
1006
|
+
wasRateLimited: wasRateLimited,
|
|
1007
|
+
tokensUsed: 1, // Token was consumed even on error!
|
|
1008
|
+
costUSD: 0.02,
|
|
1009
|
+
timestamp: new Date()
|
|
1010
|
+
})];
|
|
1011
|
+
case 12:
|
|
1012
|
+
_b.sent();
|
|
1013
|
+
_b.label = 13;
|
|
1014
|
+
case 13:
|
|
1015
|
+
if (!wasRateLimited) return [3 /*break*/, 22];
|
|
1016
|
+
log.warn(tag, "CoinGecko rate limit (".concat(status_3, ") for ").concat(coingeckoId));
|
|
1017
|
+
_b.label = 14;
|
|
1018
|
+
case 14:
|
|
1019
|
+
_b.trys.push([14, 20, , 21]);
|
|
1020
|
+
if (!(typeof require !== 'undefined')) return [3 /*break*/, 19];
|
|
1021
|
+
_b.label = 15;
|
|
1022
|
+
case 15:
|
|
1023
|
+
_b.trys.push([15, 18, , 19]);
|
|
1024
|
+
discordNotifier = require('@pioneer-platform/pioneer-server/services/discord-notifier.service').discordNotifier;
|
|
1025
|
+
if (!(discordNotifier && discordNotifier.isEnabled())) return [3 /*break*/, 17];
|
|
1026
|
+
return [4 /*yield*/, discordNotifier.sendRateLimitAlert('CoinGecko', status_3, {
|
|
1027
|
+
endpoint: '/simple/price',
|
|
1028
|
+
asset: coingeckoId
|
|
1029
|
+
})];
|
|
1030
|
+
case 16:
|
|
1031
|
+
_b.sent();
|
|
1032
|
+
_b.label = 17;
|
|
1033
|
+
case 17: return [3 /*break*/, 19];
|
|
1034
|
+
case 18:
|
|
1035
|
+
importError_1 = _b.sent();
|
|
1036
|
+
// Server context not available - running standalone, skip alert
|
|
1037
|
+
log.debug(tag, 'Discord notifier not available (standalone mode)');
|
|
1038
|
+
return [3 /*break*/, 19];
|
|
1039
|
+
case 19: return [3 /*break*/, 21];
|
|
1040
|
+
case 20:
|
|
1041
|
+
alertError_1 = _b.sent();
|
|
1042
|
+
log.debug(tag, 'Failed to send rate limit alert:', alertError_1);
|
|
1043
|
+
return [3 /*break*/, 21];
|
|
1044
|
+
case 21: return [3 /*break*/, 23];
|
|
1045
|
+
case 22:
|
|
1046
|
+
log.debug(tag, "CoinGecko error for ".concat(coingeckoId, ": ").concat(error_3.message));
|
|
1047
|
+
_b.label = 23;
|
|
1048
|
+
case 23: return [2 /*return*/, 0];
|
|
1049
|
+
case 24: return [2 /*return*/];
|
|
814
1050
|
}
|
|
815
1051
|
});
|
|
816
1052
|
});
|
|
@@ -822,7 +1058,7 @@ var get_price_from_coingecko = function (coingeckoId) {
|
|
|
822
1058
|
*/
|
|
823
1059
|
var get_price_from_coingecko_contract = function (contractAddress_1) {
|
|
824
1060
|
return __awaiter(this, arguments, void 0, function (contractAddress, chainId) {
|
|
825
|
-
var tag, url, headers, response, contractKey, price,
|
|
1061
|
+
var tag, url, headers, response, contractKey, price, error_4, status_4;
|
|
826
1062
|
var _a;
|
|
827
1063
|
if (chainId === void 0) { chainId = 'ethereum'; }
|
|
828
1064
|
return __generator(this, function (_b) {
|
|
@@ -850,13 +1086,13 @@ var get_price_from_coingecko_contract = function (contractAddress_1) {
|
|
|
850
1086
|
log.debug(tag, "No price data from CoinGecko for contract ".concat(contractAddress));
|
|
851
1087
|
return [2 /*return*/, 0];
|
|
852
1088
|
case 3:
|
|
853
|
-
|
|
854
|
-
status_4 = ((_a =
|
|
1089
|
+
error_4 = _b.sent();
|
|
1090
|
+
status_4 = ((_a = error_4.response) === null || _a === void 0 ? void 0 : _a.status) || error_4.status;
|
|
855
1091
|
if (status_4 === 429 || status_4 === 403) {
|
|
856
1092
|
log.warn(tag, "CoinGecko rate limit (".concat(status_4, ") for contract ").concat(contractAddress));
|
|
857
1093
|
}
|
|
858
1094
|
else {
|
|
859
|
-
log.debug(tag, "CoinGecko contract error for ".concat(contractAddress, ": ").concat(
|
|
1095
|
+
log.debug(tag, "CoinGecko contract error for ".concat(contractAddress, ": ").concat(error_4.message));
|
|
860
1096
|
}
|
|
861
1097
|
return [2 /*return*/, 0];
|
|
862
1098
|
case 4: return [2 /*return*/];
|
|
@@ -866,23 +1102,43 @@ var get_price_from_coingecko_contract = function (contractAddress_1) {
|
|
|
866
1102
|
};
|
|
867
1103
|
/**
|
|
868
1104
|
* Get price from CoinMarketCap by symbol
|
|
869
|
-
* Now with rate limiting
|
|
1105
|
+
* Now with token bucket rate limiting and metrics logging
|
|
870
1106
|
*/
|
|
871
1107
|
var get_price_from_coinmarketcap = function (symbol) {
|
|
872
1108
|
return __awaiter(this, void 0, void 0, function () {
|
|
873
|
-
var tag, url, response, coinData, priceData, price,
|
|
1109
|
+
var tag, startTime, hasTokens, url, response, responseTime, coinData, priceData, price, error_5, responseTime, status_5, wasRateLimited, discordNotifier, importError_2, alertError_2;
|
|
874
1110
|
var _a, _b, _c;
|
|
875
1111
|
return __generator(this, function (_d) {
|
|
876
1112
|
switch (_d.label) {
|
|
877
1113
|
case 0:
|
|
878
1114
|
tag = TAG + ' | get_price_from_coinmarketcap | ';
|
|
1115
|
+
startTime = Date.now();
|
|
879
1116
|
if (!CMC_PRO_API_KEY) {
|
|
880
1117
|
log.debug(tag, 'CMC_PRO_API_KEY not set, skipping CoinMarketCap');
|
|
881
1118
|
return [2 /*return*/, 0];
|
|
882
1119
|
}
|
|
883
|
-
|
|
1120
|
+
if (!(tokenBucketManager && tokenBucketManager.isInitialized())) return [3 /*break*/, 4];
|
|
1121
|
+
return [4 /*yield*/, tokenBucketManager.tryConsume('coinmarketcap', 1)];
|
|
884
1122
|
case 1:
|
|
885
|
-
_d.
|
|
1123
|
+
hasTokens = _d.sent();
|
|
1124
|
+
if (!!hasTokens) return [3 /*break*/, 4];
|
|
1125
|
+
log.warn(tag, '🚫 CoinMarketCap token bucket exhausted! Skipping API call.');
|
|
1126
|
+
if (!metricsLogger) return [3 /*break*/, 3];
|
|
1127
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1128
|
+
apiName: 'coinmarketcap',
|
|
1129
|
+
endpoint: '/cryptocurrency/quotes/latest',
|
|
1130
|
+
success: false,
|
|
1131
|
+
wasThrottled: true,
|
|
1132
|
+
error: 'Token bucket exhausted',
|
|
1133
|
+
tokensUsed: 0,
|
|
1134
|
+
timestamp: new Date()
|
|
1135
|
+
})];
|
|
1136
|
+
case 2:
|
|
1137
|
+
_d.sent();
|
|
1138
|
+
_d.label = 3;
|
|
1139
|
+
case 3: return [2 /*return*/, 0];
|
|
1140
|
+
case 4:
|
|
1141
|
+
_d.trys.push([4, 14, , 27]);
|
|
886
1142
|
url = "".concat(URL_COINMARKETCAP, "cryptocurrency/quotes/latest?symbol=").concat(symbol, "&convert=USD");
|
|
887
1143
|
log.debug(tag, "Fetching from CoinMarketCap: ".concat(url));
|
|
888
1144
|
return [4 /*yield*/, axios.get(url, {
|
|
@@ -891,56 +1147,164 @@ var get_price_from_coinmarketcap = function (symbol) {
|
|
|
891
1147
|
'Accept': 'application/json'
|
|
892
1148
|
}
|
|
893
1149
|
})];
|
|
894
|
-
case
|
|
1150
|
+
case 5:
|
|
895
1151
|
response = _d.sent();
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1152
|
+
responseTime = Date.now() - startTime;
|
|
1153
|
+
if (!(response.data && response.data.data && response.data.data[symbol])) return [3 /*break*/, 11];
|
|
1154
|
+
coinData = response.data.data[symbol];
|
|
1155
|
+
priceData = (_b = (_a = coinData.quote) === null || _a === void 0 ? void 0 : _a.USD) === null || _b === void 0 ? void 0 : _b.price;
|
|
1156
|
+
if (!(!priceData || isNaN(parseFloat(priceData)))) return [3 /*break*/, 8];
|
|
1157
|
+
log.warn(tag, "\u26A0\uFE0F CoinMarketCap returned invalid price for ".concat(symbol));
|
|
1158
|
+
if (!metricsLogger) return [3 /*break*/, 7];
|
|
1159
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1160
|
+
apiName: 'coinmarketcap',
|
|
1161
|
+
endpoint: '/cryptocurrency/quotes/latest',
|
|
1162
|
+
success: false,
|
|
1163
|
+
responseTime: responseTime,
|
|
1164
|
+
error: 'Invalid price data',
|
|
1165
|
+
tokensUsed: 1,
|
|
1166
|
+
costUSD: 0.015,
|
|
1167
|
+
timestamp: new Date()
|
|
1168
|
+
})];
|
|
1169
|
+
case 6:
|
|
1170
|
+
_d.sent();
|
|
1171
|
+
_d.label = 7;
|
|
1172
|
+
case 7: return [2 /*return*/, 0];
|
|
1173
|
+
case 8:
|
|
1174
|
+
price = parseFloat(priceData);
|
|
1175
|
+
log.debug(tag, "\u2705 CoinMarketCap price for ".concat(symbol, ": $").concat(price));
|
|
1176
|
+
if (!metricsLogger) return [3 /*break*/, 10];
|
|
1177
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1178
|
+
apiName: 'coinmarketcap',
|
|
1179
|
+
endpoint: '/cryptocurrency/quotes/latest',
|
|
1180
|
+
success: true,
|
|
1181
|
+
responseTime: responseTime,
|
|
1182
|
+
price: price,
|
|
1183
|
+
tokensUsed: 1,
|
|
1184
|
+
costUSD: 0.015,
|
|
1185
|
+
timestamp: new Date()
|
|
1186
|
+
})];
|
|
1187
|
+
case 9:
|
|
1188
|
+
_d.sent();
|
|
1189
|
+
_d.label = 10;
|
|
1190
|
+
case 10: return [2 /*return*/, price];
|
|
1191
|
+
case 11:
|
|
908
1192
|
log.debug(tag, "No price data from CoinMarketCap for ".concat(symbol));
|
|
909
|
-
return [
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
case
|
|
1193
|
+
if (!metricsLogger) return [3 /*break*/, 13];
|
|
1194
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1195
|
+
apiName: 'coinmarketcap',
|
|
1196
|
+
endpoint: '/cryptocurrency/quotes/latest',
|
|
1197
|
+
success: false,
|
|
1198
|
+
responseTime: responseTime,
|
|
1199
|
+
error: 'No price data',
|
|
1200
|
+
tokensUsed: 1,
|
|
1201
|
+
costUSD: 0.015,
|
|
1202
|
+
timestamp: new Date()
|
|
1203
|
+
})];
|
|
1204
|
+
case 12:
|
|
1205
|
+
_d.sent();
|
|
1206
|
+
_d.label = 13;
|
|
1207
|
+
case 13: return [2 /*return*/, 0];
|
|
1208
|
+
case 14:
|
|
1209
|
+
error_5 = _d.sent();
|
|
1210
|
+
responseTime = Date.now() - startTime;
|
|
1211
|
+
status_5 = ((_c = error_5.response) === null || _c === void 0 ? void 0 : _c.status) || error_5.status;
|
|
1212
|
+
wasRateLimited = status_5 === 429 || status_5 === 403;
|
|
1213
|
+
if (!metricsLogger) return [3 /*break*/, 16];
|
|
1214
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1215
|
+
apiName: 'coinmarketcap',
|
|
1216
|
+
endpoint: '/cryptocurrency/quotes/latest',
|
|
1217
|
+
success: false,
|
|
1218
|
+
responseTime: responseTime,
|
|
1219
|
+
statusCode: status_5,
|
|
1220
|
+
error: error_5.message,
|
|
1221
|
+
wasRateLimited: wasRateLimited,
|
|
1222
|
+
tokensUsed: 1,
|
|
1223
|
+
costUSD: 0.015,
|
|
1224
|
+
timestamp: new Date()
|
|
1225
|
+
})];
|
|
1226
|
+
case 15:
|
|
1227
|
+
_d.sent();
|
|
1228
|
+
_d.label = 16;
|
|
1229
|
+
case 16:
|
|
1230
|
+
if (!wasRateLimited) return [3 /*break*/, 25];
|
|
1231
|
+
log.warn(tag, "CoinMarketCap rate limit (".concat(status_5, ") for ").concat(symbol));
|
|
1232
|
+
_d.label = 17;
|
|
1233
|
+
case 17:
|
|
1234
|
+
_d.trys.push([17, 23, , 24]);
|
|
1235
|
+
if (!(typeof require !== 'undefined')) return [3 /*break*/, 22];
|
|
1236
|
+
_d.label = 18;
|
|
1237
|
+
case 18:
|
|
1238
|
+
_d.trys.push([18, 21, , 22]);
|
|
1239
|
+
discordNotifier = require('@pioneer-platform/pioneer-server/services/discord-notifier.service').discordNotifier;
|
|
1240
|
+
if (!(discordNotifier && discordNotifier.isEnabled())) return [3 /*break*/, 20];
|
|
1241
|
+
return [4 /*yield*/, discordNotifier.sendRateLimitAlert('CoinMarketCap', status_5, {
|
|
1242
|
+
endpoint: '/cryptocurrency/quotes/latest',
|
|
1243
|
+
symbol: symbol
|
|
1244
|
+
})];
|
|
1245
|
+
case 19:
|
|
1246
|
+
_d.sent();
|
|
1247
|
+
_d.label = 20;
|
|
1248
|
+
case 20: return [3 /*break*/, 22];
|
|
1249
|
+
case 21:
|
|
1250
|
+
importError_2 = _d.sent();
|
|
1251
|
+
// Server context not available - running standalone, skip alert
|
|
1252
|
+
log.debug(tag, 'Discord notifier not available (standalone mode)');
|
|
1253
|
+
return [3 /*break*/, 22];
|
|
1254
|
+
case 22: return [3 /*break*/, 24];
|
|
1255
|
+
case 23:
|
|
1256
|
+
alertError_2 = _d.sent();
|
|
1257
|
+
log.debug(tag, 'Failed to send rate limit alert:', alertError_2);
|
|
1258
|
+
return [3 /*break*/, 24];
|
|
1259
|
+
case 24: return [3 /*break*/, 26];
|
|
1260
|
+
case 25:
|
|
1261
|
+
log.debug(tag, "CoinMarketCap error for ".concat(symbol, ": ").concat(error_5.message));
|
|
1262
|
+
_d.label = 26;
|
|
1263
|
+
case 26: return [2 /*return*/, 0];
|
|
1264
|
+
case 27: return [2 /*return*/];
|
|
921
1265
|
}
|
|
922
1266
|
});
|
|
923
1267
|
});
|
|
924
1268
|
};
|
|
925
1269
|
/**
|
|
926
1270
|
* Get price from CoinCap by symbol
|
|
927
|
-
* Now with rate limiting
|
|
1271
|
+
* Now with token bucket rate limiting and metrics logging
|
|
928
1272
|
*/
|
|
929
1273
|
var get_price_from_coincap = function (symbol) {
|
|
930
1274
|
return __awaiter(this, void 0, void 0, function () {
|
|
931
|
-
var tag, url, response, price,
|
|
1275
|
+
var tag, startTime, hasTokens, url, response, responseTime, price, error_6, responseTime, status_6, wasRateLimited, discordNotifier, importError_3, alertError_3;
|
|
932
1276
|
var _a;
|
|
933
1277
|
return __generator(this, function (_b) {
|
|
934
1278
|
switch (_b.label) {
|
|
935
1279
|
case 0:
|
|
936
1280
|
tag = TAG + ' | get_price_from_coincap | ';
|
|
1281
|
+
startTime = Date.now();
|
|
937
1282
|
if (!COINCAP_API_KEY) {
|
|
938
1283
|
log.debug(tag, 'COINCAP_API_KEY not set, skipping CoinCap');
|
|
939
1284
|
return [2 /*return*/, 0];
|
|
940
1285
|
}
|
|
941
|
-
|
|
1286
|
+
if (!(tokenBucketManager && tokenBucketManager.isInitialized())) return [3 /*break*/, 4];
|
|
1287
|
+
return [4 /*yield*/, tokenBucketManager.tryConsume('coincap', 1)];
|
|
942
1288
|
case 1:
|
|
943
|
-
_b.
|
|
1289
|
+
hasTokens = _b.sent();
|
|
1290
|
+
if (!!hasTokens) return [3 /*break*/, 4];
|
|
1291
|
+
log.warn(tag, '🚫 CoinCap token bucket exhausted! Skipping API call.');
|
|
1292
|
+
if (!metricsLogger) return [3 /*break*/, 3];
|
|
1293
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1294
|
+
apiName: 'coincap',
|
|
1295
|
+
endpoint: '/assets',
|
|
1296
|
+
success: false,
|
|
1297
|
+
wasThrottled: true,
|
|
1298
|
+
error: 'Token bucket exhausted',
|
|
1299
|
+
tokensUsed: 0,
|
|
1300
|
+
timestamp: new Date()
|
|
1301
|
+
})];
|
|
1302
|
+
case 2:
|
|
1303
|
+
_b.sent();
|
|
1304
|
+
_b.label = 3;
|
|
1305
|
+
case 3: return [2 /*return*/, 0];
|
|
1306
|
+
case 4:
|
|
1307
|
+
_b.trys.push([4, 11, , 24]);
|
|
944
1308
|
url = "".concat(URL_COINCAP, "assets/").concat(symbol);
|
|
945
1309
|
log.debug(tag, "Fetching from CoinCap: ".concat(url));
|
|
946
1310
|
return [4 /*yield*/, axios.get(url, {
|
|
@@ -949,26 +1313,101 @@ var get_price_from_coincap = function (symbol) {
|
|
|
949
1313
|
'Authorization': "Bearer ".concat(COINCAP_API_KEY)
|
|
950
1314
|
}
|
|
951
1315
|
})];
|
|
952
|
-
case
|
|
1316
|
+
case 5:
|
|
953
1317
|
response = _b.sent();
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1318
|
+
responseTime = Date.now() - startTime;
|
|
1319
|
+
if (!(response.data && response.data.data && response.data.data.priceUsd)) return [3 /*break*/, 8];
|
|
1320
|
+
price = parseFloat(response.data.data.priceUsd);
|
|
1321
|
+
log.debug(tag, "\u2705 CoinCap price for ".concat(symbol, ": $").concat(price));
|
|
1322
|
+
if (!metricsLogger) return [3 /*break*/, 7];
|
|
1323
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1324
|
+
apiName: 'coincap',
|
|
1325
|
+
endpoint: '/assets',
|
|
1326
|
+
success: true,
|
|
1327
|
+
responseTime: responseTime,
|
|
1328
|
+
price: price,
|
|
1329
|
+
tokensUsed: 1,
|
|
1330
|
+
costUSD: 0.01,
|
|
1331
|
+
timestamp: new Date()
|
|
1332
|
+
})];
|
|
1333
|
+
case 6:
|
|
1334
|
+
_b.sent();
|
|
1335
|
+
_b.label = 7;
|
|
1336
|
+
case 7: return [2 /*return*/, price];
|
|
1337
|
+
case 8:
|
|
959
1338
|
log.debug(tag, "No price data from CoinCap for ".concat(symbol));
|
|
960
|
-
return [
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
case
|
|
1339
|
+
if (!metricsLogger) return [3 /*break*/, 10];
|
|
1340
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1341
|
+
apiName: 'coincap',
|
|
1342
|
+
endpoint: '/assets',
|
|
1343
|
+
success: false,
|
|
1344
|
+
responseTime: responseTime,
|
|
1345
|
+
error: 'No price data',
|
|
1346
|
+
tokensUsed: 1,
|
|
1347
|
+
costUSD: 0.01,
|
|
1348
|
+
timestamp: new Date()
|
|
1349
|
+
})];
|
|
1350
|
+
case 9:
|
|
1351
|
+
_b.sent();
|
|
1352
|
+
_b.label = 10;
|
|
1353
|
+
case 10: return [2 /*return*/, 0];
|
|
1354
|
+
case 11:
|
|
1355
|
+
error_6 = _b.sent();
|
|
1356
|
+
responseTime = Date.now() - startTime;
|
|
1357
|
+
status_6 = ((_a = error_6.response) === null || _a === void 0 ? void 0 : _a.status) || error_6.status;
|
|
1358
|
+
wasRateLimited = status_6 === 429 || status_6 === 403;
|
|
1359
|
+
if (!metricsLogger) return [3 /*break*/, 13];
|
|
1360
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
1361
|
+
apiName: 'coincap',
|
|
1362
|
+
endpoint: '/assets',
|
|
1363
|
+
success: false,
|
|
1364
|
+
responseTime: responseTime,
|
|
1365
|
+
statusCode: status_6,
|
|
1366
|
+
error: error_6.message,
|
|
1367
|
+
wasRateLimited: wasRateLimited,
|
|
1368
|
+
tokensUsed: 1,
|
|
1369
|
+
costUSD: 0.01,
|
|
1370
|
+
timestamp: new Date()
|
|
1371
|
+
})];
|
|
1372
|
+
case 12:
|
|
1373
|
+
_b.sent();
|
|
1374
|
+
_b.label = 13;
|
|
1375
|
+
case 13:
|
|
1376
|
+
if (!wasRateLimited) return [3 /*break*/, 22];
|
|
1377
|
+
log.warn(tag, "CoinCap rate limit (".concat(status_6, ") for ").concat(symbol));
|
|
1378
|
+
_b.label = 14;
|
|
1379
|
+
case 14:
|
|
1380
|
+
_b.trys.push([14, 20, , 21]);
|
|
1381
|
+
if (!(typeof require !== 'undefined')) return [3 /*break*/, 19];
|
|
1382
|
+
_b.label = 15;
|
|
1383
|
+
case 15:
|
|
1384
|
+
_b.trys.push([15, 18, , 19]);
|
|
1385
|
+
discordNotifier = require('@pioneer-platform/pioneer-server/services/discord-notifier.service').discordNotifier;
|
|
1386
|
+
if (!(discordNotifier && discordNotifier.isEnabled())) return [3 /*break*/, 17];
|
|
1387
|
+
return [4 /*yield*/, discordNotifier.sendRateLimitAlert('CoinCap', status_6, {
|
|
1388
|
+
endpoint: '/assets',
|
|
1389
|
+
symbol: symbol
|
|
1390
|
+
})];
|
|
1391
|
+
case 16:
|
|
1392
|
+
_b.sent();
|
|
1393
|
+
_b.label = 17;
|
|
1394
|
+
case 17: return [3 /*break*/, 19];
|
|
1395
|
+
case 18:
|
|
1396
|
+
importError_3 = _b.sent();
|
|
1397
|
+
// Server context not available - running standalone, skip alert
|
|
1398
|
+
log.debug(tag, 'Discord notifier not available (standalone mode)');
|
|
1399
|
+
return [3 /*break*/, 19];
|
|
1400
|
+
case 19: return [3 /*break*/, 21];
|
|
1401
|
+
case 20:
|
|
1402
|
+
alertError_3 = _b.sent();
|
|
1403
|
+
log.debug(tag, 'Failed to send rate limit alert:', alertError_3);
|
|
1404
|
+
return [3 /*break*/, 21];
|
|
1405
|
+
case 21: return [3 /*break*/, 23];
|
|
1406
|
+
case 22:
|
|
1407
|
+
log.debug(tag, "CoinCap error for ".concat(symbol, ": ").concat(error_6.message));
|
|
1408
|
+
_b.label = 23;
|
|
1409
|
+
case 23: return [2 /*return*/, 0];
|
|
1410
|
+
case 24: return [2 /*return*/];
|
|
972
1411
|
}
|
|
973
1412
|
});
|
|
974
1413
|
});
|
|
@@ -980,7 +1419,7 @@ var get_price_from_coincap = function (symbol) {
|
|
|
980
1419
|
*/
|
|
981
1420
|
var get_price_from_mayascan = function () {
|
|
982
1421
|
return __awaiter(this, arguments, void 0, function (asset) {
|
|
983
|
-
var tag, url, response, price,
|
|
1422
|
+
var tag, url, response, price, error_7;
|
|
984
1423
|
if (asset === void 0) { asset = 'maya'; }
|
|
985
1424
|
return __generator(this, function (_a) {
|
|
986
1425
|
switch (_a.label) {
|
|
@@ -1010,8 +1449,8 @@ var get_price_from_mayascan = function () {
|
|
|
1010
1449
|
log.debug(tag, "No ".concat(asset.toUpperCase(), " price data from MayaScan"));
|
|
1011
1450
|
return [2 /*return*/, 0];
|
|
1012
1451
|
case 3:
|
|
1013
|
-
|
|
1014
|
-
log.debug(tag, "MayaScan error: ".concat(
|
|
1452
|
+
error_7 = _a.sent();
|
|
1453
|
+
log.debug(tag, "MayaScan error: ".concat(error_7.message));
|
|
1015
1454
|
return [2 /*return*/, 0];
|
|
1016
1455
|
case 4: return [2 /*return*/];
|
|
1017
1456
|
}
|
|
@@ -1052,7 +1491,8 @@ var get_asset_price_by_caip = function (caip_2) {
|
|
|
1052
1491
|
*/
|
|
1053
1492
|
var get_asset_price_by_caip_internal = function (caip_2) {
|
|
1054
1493
|
return __awaiter(this, arguments, void 0, function (caip, returnSource) {
|
|
1055
|
-
var tag, mayaPrice, isCacao, identifiers, price, contractAddress, chainIdMatch, chainId, chainMap, platformName, asset, symbol, cacaoPrice;
|
|
1494
|
+
var tag, mayaPrice, isCacao, identifiers, price, apiStrategies, shuffledStrategies, _i, shuffledStrategies_1, strategy, error_8, contractAddress, chainIdMatch, chainId, chainMap, platformName, asset, symbol, cacaoPrice, ccxtPrice, ccxtError_1;
|
|
1495
|
+
var _this = this;
|
|
1056
1496
|
if (returnSource === void 0) { returnSource = false; }
|
|
1057
1497
|
return __generator(this, function (_a) {
|
|
1058
1498
|
switch (_a.label) {
|
|
@@ -1078,26 +1518,138 @@ var get_asset_price_by_caip_internal = function (caip_2) {
|
|
|
1078
1518
|
isCacao = caip === 'cosmos:mayachain-mainnet-v1/slip44:931';
|
|
1079
1519
|
identifiers = caip_to_identifiers(caip);
|
|
1080
1520
|
price = 0;
|
|
1081
|
-
if (!identifiers) return [3 /*break*/,
|
|
1521
|
+
if (!identifiers) return [3 /*break*/, 14];
|
|
1082
1522
|
log.debug(tag, "Identifiers for ".concat(caip, ":"), identifiers);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1523
|
+
apiStrategies = [
|
|
1524
|
+
{
|
|
1525
|
+
name: 'CoinGecko',
|
|
1526
|
+
icon: '🦎',
|
|
1527
|
+
fetch: function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1528
|
+
var p;
|
|
1529
|
+
return __generator(this, function (_a) {
|
|
1530
|
+
switch (_a.label) {
|
|
1531
|
+
case 0:
|
|
1532
|
+
console.log("\uD83E\uDD8E Trying CoinGecko with mapped ID: ".concat(identifiers.coingeckoId));
|
|
1533
|
+
return [4 /*yield*/, get_price_from_coingecko(identifiers.coingeckoId)];
|
|
1534
|
+
case 1:
|
|
1535
|
+
p = _a.sent();
|
|
1536
|
+
console.log("\uD83E\uDD8E CoinGecko returned price: $".concat(p));
|
|
1537
|
+
return [2 /*return*/, p];
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
}); },
|
|
1541
|
+
source: 'coingecko'
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
name: 'CoinMarketCap',
|
|
1545
|
+
icon: '📊',
|
|
1546
|
+
fetch: function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1547
|
+
var p;
|
|
1548
|
+
return __generator(this, function (_a) {
|
|
1549
|
+
switch (_a.label) {
|
|
1550
|
+
case 0:
|
|
1551
|
+
console.log("\uD83D\uDCCA Trying CoinMarketCap for: ".concat(identifiers.cmcSymbol));
|
|
1552
|
+
return [4 /*yield*/, get_price_from_coinmarketcap(identifiers.cmcSymbol)];
|
|
1553
|
+
case 1:
|
|
1554
|
+
p = _a.sent();
|
|
1555
|
+
console.log("\uD83D\uDCCA CoinMarketCap returned price: $".concat(p));
|
|
1556
|
+
return [2 /*return*/, p];
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
}); },
|
|
1560
|
+
source: 'coinmarketcap'
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
name: 'CoinCap',
|
|
1564
|
+
icon: '💰',
|
|
1565
|
+
fetch: function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1566
|
+
var p;
|
|
1567
|
+
return __generator(this, function (_a) {
|
|
1568
|
+
switch (_a.label) {
|
|
1569
|
+
case 0:
|
|
1570
|
+
console.log("\uD83D\uDCB0 Trying CoinCap for: ".concat(identifiers.coincapSymbol));
|
|
1571
|
+
return [4 /*yield*/, get_price_from_coincap(identifiers.coincapSymbol)];
|
|
1572
|
+
case 1:
|
|
1573
|
+
p = _a.sent();
|
|
1574
|
+
console.log("\uD83D\uDCB0 CoinCap returned price: $".concat(p));
|
|
1575
|
+
return [2 /*return*/, p];
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
}); },
|
|
1579
|
+
source: 'coincap'
|
|
1580
|
+
},
|
|
1581
|
+
{
|
|
1582
|
+
name: 'CCXT',
|
|
1583
|
+
icon: '🔄',
|
|
1584
|
+
fetch: function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1585
|
+
var p;
|
|
1586
|
+
return __generator(this, function (_a) {
|
|
1587
|
+
switch (_a.label) {
|
|
1588
|
+
case 0:
|
|
1589
|
+
console.log("\uD83D\uDD04 Trying CCXT for: ".concat(identifiers.symbol));
|
|
1590
|
+
return [4 /*yield*/, (0, ccxt_pricing_1.getPriceFromCCXT)(identifiers.symbol)];
|
|
1591
|
+
case 1:
|
|
1592
|
+
p = _a.sent();
|
|
1593
|
+
console.log("\uD83D\uDD04 CCXT returned price: $".concat(p));
|
|
1594
|
+
return [2 /*return*/, p];
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
}); },
|
|
1598
|
+
source: 'ccxt'
|
|
1599
|
+
}
|
|
1600
|
+
];
|
|
1601
|
+
shuffledStrategies = apiStrategies.sort(function () { return Math.random() - 0.5; });
|
|
1602
|
+
log.debug(tag, "Randomized API order: ".concat(shuffledStrategies.map(function (s) { return s.name; }).join(' → ')));
|
|
1603
|
+
_i = 0, shuffledStrategies_1 = shuffledStrategies;
|
|
1604
|
+
_a.label = 3;
|
|
1086
1605
|
case 3:
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
return [2 /*return*/, returnSource ? { price: price, source: 'coingecko' } : price];
|
|
1091
|
-
}
|
|
1092
|
-
return [3 /*break*/, 5];
|
|
1606
|
+
if (!(_i < shuffledStrategies_1.length)) return [3 /*break*/, 13];
|
|
1607
|
+
strategy = shuffledStrategies_1[_i];
|
|
1608
|
+
_a.label = 4;
|
|
1093
1609
|
case 4:
|
|
1094
|
-
|
|
1095
|
-
|
|
1610
|
+
_a.trys.push([4, 10, , 12]);
|
|
1611
|
+
return [4 /*yield*/, strategy.fetch()];
|
|
1096
1612
|
case 5:
|
|
1097
|
-
|
|
1613
|
+
price = _a.sent();
|
|
1614
|
+
if (!(price > 0)) return [3 /*break*/, 7];
|
|
1615
|
+
log.info(tag, "\u2705 Got price from ".concat(strategy.name, ": $").concat(price));
|
|
1616
|
+
// Track successful API call
|
|
1617
|
+
return [4 /*yield*/, trackAPIMetric(strategy.source, true)];
|
|
1618
|
+
case 6:
|
|
1619
|
+
// Track successful API call
|
|
1620
|
+
_a.sent();
|
|
1621
|
+
return [2 /*return*/, returnSource ? { price: price, source: strategy.source } : price];
|
|
1622
|
+
case 7:
|
|
1623
|
+
// Track failure (0 price returned)
|
|
1624
|
+
return [4 /*yield*/, trackAPIMetric(strategy.source, false, 'Returned 0 price')];
|
|
1625
|
+
case 8:
|
|
1626
|
+
// Track failure (0 price returned)
|
|
1627
|
+
_a.sent();
|
|
1628
|
+
_a.label = 9;
|
|
1629
|
+
case 9: return [3 /*break*/, 12];
|
|
1630
|
+
case 10:
|
|
1631
|
+
error_8 = _a.sent();
|
|
1632
|
+
log.debug(tag, "".concat(strategy.name, " failed:"), error_8);
|
|
1633
|
+
// Track failure with error message
|
|
1634
|
+
return [4 /*yield*/, trackAPIMetric(strategy.source, false, error_8.message)];
|
|
1635
|
+
case 11:
|
|
1636
|
+
// Track failure with error message
|
|
1637
|
+
_a.sent();
|
|
1638
|
+
return [3 /*break*/, 12];
|
|
1639
|
+
case 12:
|
|
1640
|
+
_i++;
|
|
1641
|
+
return [3 /*break*/, 3];
|
|
1642
|
+
case 13:
|
|
1643
|
+
log.debug(tag, "All APIs returned 0 or failed for ".concat(caip));
|
|
1644
|
+
return [3 /*break*/, 15];
|
|
1645
|
+
case 14:
|
|
1646
|
+
log.debug(tag, "No identifier mapping found for CAIP: ".concat(caip));
|
|
1647
|
+
_a.label = 15;
|
|
1648
|
+
case 15:
|
|
1649
|
+
if (!caip.includes('/erc20:0x')) return [3 /*break*/, 23];
|
|
1098
1650
|
contractAddress = caip.split('/erc20:')[1];
|
|
1099
1651
|
chainIdMatch = caip.match(/eip155:(\d+)/);
|
|
1100
|
-
if (!(contractAddress && chainIdMatch)) return [3 /*break*/,
|
|
1652
|
+
if (!(contractAddress && chainIdMatch)) return [3 /*break*/, 17];
|
|
1101
1653
|
chainId = chainIdMatch[1];
|
|
1102
1654
|
chainMap = {
|
|
1103
1655
|
'1': 'ethereum',
|
|
@@ -1112,23 +1664,23 @@ var get_asset_price_by_caip_internal = function (caip_2) {
|
|
|
1112
1664
|
platformName = chainMap[chainId] || 'ethereum';
|
|
1113
1665
|
console.log("\uD83D\uDD04 Trying CoinGecko contract lookup: ".concat(contractAddress, " on ").concat(platformName));
|
|
1114
1666
|
return [4 /*yield*/, get_price_from_coingecko_contract(contractAddress, platformName)];
|
|
1115
|
-
case
|
|
1667
|
+
case 16:
|
|
1116
1668
|
price = _a.sent();
|
|
1117
1669
|
console.log("\uD83D\uDD04 CoinGecko contract returned price: $".concat(price));
|
|
1118
1670
|
if (price > 0) {
|
|
1119
1671
|
return [2 /*return*/, returnSource ? { price: price, source: 'coingecko-contract' } : price];
|
|
1120
1672
|
}
|
|
1121
|
-
_a.label =
|
|
1122
|
-
case
|
|
1123
|
-
if (!(price === 0)) return [3 /*break*/,
|
|
1673
|
+
_a.label = 17;
|
|
1674
|
+
case 17:
|
|
1675
|
+
if (!(price === 0)) return [3 /*break*/, 23];
|
|
1124
1676
|
asset = pioneer_discovery_1.assetData[caip] || pioneer_discovery_1.assetData[caip.toUpperCase()] || pioneer_discovery_1.assetData[caip.toLowerCase()];
|
|
1125
|
-
if (!(asset && asset.symbol)) return [3 /*break*/,
|
|
1677
|
+
if (!(asset && asset.symbol)) return [3 /*break*/, 21];
|
|
1126
1678
|
symbol = asset.symbol.toUpperCase();
|
|
1127
1679
|
log.info(tag, "\uD83D\uDD04 Contract lookup failed, falling back to symbol-based APIs for: ".concat(symbol));
|
|
1128
1680
|
// Try CoinMarketCap
|
|
1129
1681
|
console.log("\uD83D\uDCCA Trying CoinMarketCap for ERC20 token: ".concat(symbol));
|
|
1130
1682
|
return [4 /*yield*/, get_price_from_coinmarketcap(symbol)];
|
|
1131
|
-
case
|
|
1683
|
+
case 18:
|
|
1132
1684
|
price = _a.sent();
|
|
1133
1685
|
console.log("\uD83D\uDCCA CoinMarketCap returned price: $".concat(price));
|
|
1134
1686
|
if (price > 0) {
|
|
@@ -1137,7 +1689,7 @@ var get_asset_price_by_caip_internal = function (caip_2) {
|
|
|
1137
1689
|
// Try CoinCap
|
|
1138
1690
|
console.log("\uD83D\uDCB0 Trying CoinCap for ERC20 token: ".concat(symbol.toLowerCase()));
|
|
1139
1691
|
return [4 /*yield*/, get_price_from_coincap(symbol.toLowerCase())];
|
|
1140
|
-
case
|
|
1692
|
+
case 19:
|
|
1141
1693
|
price = _a.sent();
|
|
1142
1694
|
console.log("\uD83D\uDCB0 CoinCap returned price: $".concat(price));
|
|
1143
1695
|
if (price > 0) {
|
|
@@ -1145,38 +1697,52 @@ var get_asset_price_by_caip_internal = function (caip_2) {
|
|
|
1145
1697
|
}
|
|
1146
1698
|
// All 3 paid APIs failed - mark as unpriceable (likely scam/spam token)
|
|
1147
1699
|
log.warn(tag, "\u274C Token not found in any of 3 paid APIs (CoinGecko, CMC, CoinCap): ".concat(caip));
|
|
1700
|
+
// CRITICAL: Double-check this isn't a major crypto before marking unpriceable
|
|
1701
|
+
if (MAJOR_CRYPTO_WHITELIST.has(caip)) {
|
|
1702
|
+
log.error(tag, "\uD83D\uDEA8 CRITICAL: Major crypto ".concat(caip, " failed all API lookups!"));
|
|
1703
|
+
log.error(tag, "This indicates API outage or rate limiting - NOT marking as unpriceable");
|
|
1704
|
+
return [2 /*return*/, returnSource ? { price: 0, source: 'api_failure' } : 0];
|
|
1705
|
+
}
|
|
1148
1706
|
log.warn(tag, "\uD83D\uDEA8 Marking as maybeScam - will not retry pricing for this token");
|
|
1149
1707
|
// Cache the failure so we never try again
|
|
1150
1708
|
return [4 /*yield*/, cache_unpriceable_token(caip)];
|
|
1151
|
-
case
|
|
1709
|
+
case 20:
|
|
1152
1710
|
// Cache the failure so we never try again
|
|
1153
1711
|
_a.sent();
|
|
1154
1712
|
return [2 /*return*/, returnSource ? { price: 0, source: 'unpriceable', maybeScam: true } : 0];
|
|
1155
|
-
case
|
|
1713
|
+
case 21:
|
|
1156
1714
|
// Token not in our discovery database - can't get symbol for API calls
|
|
1157
1715
|
log.warn(tag, "\u274C Token not in discovery database, cannot lookup symbol: ".concat(caip));
|
|
1158
1716
|
log.warn(tag, "\uD83D\uDEA8 Marking as maybeScam - likely user-added spam token");
|
|
1159
1717
|
// Cache the failure so we never try again
|
|
1160
1718
|
return [4 /*yield*/, cache_unpriceable_token(caip)];
|
|
1161
|
-
case
|
|
1719
|
+
case 22:
|
|
1162
1720
|
// Cache the failure so we never try again
|
|
1163
1721
|
_a.sent();
|
|
1164
1722
|
return [2 /*return*/, returnSource ? { price: 0, source: 'unpriceable', maybeScam: true } : 0];
|
|
1165
|
-
case
|
|
1166
|
-
if (!!identifiers) return [3 /*break*/,
|
|
1723
|
+
case 23:
|
|
1724
|
+
if (!!identifiers) return [3 /*break*/, 25];
|
|
1167
1725
|
log.warn(tag, "No way to lookup price for CAIP: ".concat(caip, " (no mapping, not ERC20, or all lookups failed)"));
|
|
1726
|
+
// CRITICAL: Never mark major cryptocurrencies as unpriceable!
|
|
1727
|
+
// This is a second layer of protection in case whitelist check fails
|
|
1728
|
+
if (MAJOR_CRYPTO_WHITELIST.has(caip)) {
|
|
1729
|
+
log.error(tag, "\uD83D\uDEA8 MAJOR CRYPTO WITHOUT IDENTIFIERS: ".concat(caip));
|
|
1730
|
+
log.error(tag, "This should NEVER happen - check coingeckoMapping and assetData");
|
|
1731
|
+
log.error(tag, "Returning 0 but NOT caching as unpriceable");
|
|
1732
|
+
return [2 /*return*/, returnSource ? { price: 0, source: 'mapping_error' } : 0];
|
|
1733
|
+
}
|
|
1168
1734
|
log.warn(tag, "\uD83D\uDEA8 Marking as maybeScam - token has no price source");
|
|
1169
1735
|
// Cache the failure so we never try again
|
|
1170
1736
|
return [4 /*yield*/, cache_unpriceable_token(caip)];
|
|
1171
|
-
case
|
|
1737
|
+
case 24:
|
|
1172
1738
|
// Cache the failure so we never try again
|
|
1173
1739
|
_a.sent();
|
|
1174
1740
|
return [2 /*return*/, returnSource ? { price: 0, source: 'unpriceable', maybeScam: true } : 0];
|
|
1175
|
-
case
|
|
1741
|
+
case 25:
|
|
1176
1742
|
// STEP 4: Try CoinMarketCap by symbol (requires API key)
|
|
1177
1743
|
console.log("\uD83D\uDCCA Trying CoinMarketCap for: ".concat(identifiers.cmcSymbol));
|
|
1178
1744
|
return [4 /*yield*/, get_price_from_coinmarketcap(identifiers.cmcSymbol)];
|
|
1179
|
-
case
|
|
1745
|
+
case 26:
|
|
1180
1746
|
price = _a.sent();
|
|
1181
1747
|
console.log("\uD83D\uDCCA CoinMarketCap returned price: $".concat(price));
|
|
1182
1748
|
if (price > 0) {
|
|
@@ -1185,23 +1751,41 @@ var get_asset_price_by_caip_internal = function (caip_2) {
|
|
|
1185
1751
|
// STEP 5: Try CoinCap by symbol (last resort)
|
|
1186
1752
|
console.log("\uD83D\uDCB0 Trying CoinCap for: ".concat(identifiers.coincapSymbol));
|
|
1187
1753
|
return [4 /*yield*/, get_price_from_coincap(identifiers.coincapSymbol)];
|
|
1188
|
-
case
|
|
1754
|
+
case 27:
|
|
1189
1755
|
price = _a.sent();
|
|
1190
1756
|
console.log("\uD83D\uDCB0 CoinCap returned price: $".concat(price));
|
|
1191
1757
|
if (price > 0) {
|
|
1192
1758
|
return [2 /*return*/, returnSource ? { price: price, source: 'coincap' } : price];
|
|
1193
1759
|
}
|
|
1194
|
-
if (!isCacao) return [3 /*break*/,
|
|
1760
|
+
if (!isCacao) return [3 /*break*/, 29];
|
|
1195
1761
|
log.debug(tag, '🏔️ All pricing APIs failed for CACAO, trying MayaScan as final fallback');
|
|
1196
1762
|
return [4 /*yield*/, get_price_from_mayascan('cacao')];
|
|
1197
|
-
case
|
|
1763
|
+
case 28:
|
|
1198
1764
|
cacaoPrice = _a.sent();
|
|
1199
1765
|
if (cacaoPrice > 0) {
|
|
1200
1766
|
return [2 /*return*/, returnSource ? { price: cacaoPrice, source: 'mayascan' } : cacaoPrice];
|
|
1201
1767
|
}
|
|
1202
|
-
_a.label =
|
|
1203
|
-
case
|
|
1204
|
-
|
|
1768
|
+
_a.label = 29;
|
|
1769
|
+
case 29:
|
|
1770
|
+
if (!(identifiers && identifiers.symbol)) return [3 /*break*/, 33];
|
|
1771
|
+
log.debug(tag, "\uD83D\uDD04 Trying CCXT as final fallback for: ".concat(identifiers.symbol));
|
|
1772
|
+
_a.label = 30;
|
|
1773
|
+
case 30:
|
|
1774
|
+
_a.trys.push([30, 32, , 33]);
|
|
1775
|
+
return [4 /*yield*/, (0, ccxt_pricing_1.getPriceFromCCXT)(identifiers.symbol)];
|
|
1776
|
+
case 31:
|
|
1777
|
+
ccxtPrice = _a.sent();
|
|
1778
|
+
if (ccxtPrice > 0) {
|
|
1779
|
+
log.info(tag, "\u2705 Got price from CCXT for ".concat(caip, ": $").concat(ccxtPrice));
|
|
1780
|
+
return [2 /*return*/, returnSource ? { price: ccxtPrice, source: 'ccxt' } : ccxtPrice];
|
|
1781
|
+
}
|
|
1782
|
+
return [3 /*break*/, 33];
|
|
1783
|
+
case 32:
|
|
1784
|
+
ccxtError_1 = _a.sent();
|
|
1785
|
+
log.debug(tag, "CCXT lookup failed for ".concat(identifiers.symbol, ":"), ccxtError_1);
|
|
1786
|
+
return [3 /*break*/, 33];
|
|
1787
|
+
case 33:
|
|
1788
|
+
log.warn(tag, "\u274C No price found from any API (including CCXT) for: ".concat(caip));
|
|
1205
1789
|
return [2 /*return*/, returnSource ? { price: 0, source: 'none' } : 0];
|
|
1206
1790
|
}
|
|
1207
1791
|
});
|
|
@@ -1346,7 +1930,7 @@ var get_price = function (asset) {
|
|
|
1346
1930
|
*/
|
|
1347
1931
|
var get_batch_prices_by_caip = function (caips_1) {
|
|
1348
1932
|
return __awaiter(this, arguments, void 0, function (caips, returnSource) {
|
|
1349
|
-
var tag, results, caipToIdMap, specialCaips, _i, caips_2, caip, identifiers, coingeckoIds, batchSize, i, batch, idsParam, url, response, _a, batch_1, coingeckoId, mapping, price,
|
|
1933
|
+
var tag, results, caipToIdMap, specialCaips, _i, caips_2, caip, identifiers, coingeckoIds, batchSize, i, batch, idsParam, url, response, _a, batch_1, coingeckoId, mapping, price, error_9, _b, specialCaips_1, caip, price, error_10, missingCaips, _c, missingCaips_1, caip, _d, _e, error_11;
|
|
1350
1934
|
var _f;
|
|
1351
1935
|
if (returnSource === void 0) { returnSource = false; }
|
|
1352
1936
|
return __generator(this, function (_g) {
|
|
@@ -1413,8 +1997,8 @@ var get_batch_prices_by_caip = function (caips_1) {
|
|
|
1413
1997
|
log.info(tag, "\u2705 Batch fetched ".concat(Object.keys(results).length, " prices from CoinGecko"));
|
|
1414
1998
|
return [3 /*break*/, 8];
|
|
1415
1999
|
case 7:
|
|
1416
|
-
|
|
1417
|
-
log.error(tag, "CoinGecko batch fetch failed:",
|
|
2000
|
+
error_9 = _g.sent();
|
|
2001
|
+
log.error(tag, "CoinGecko batch fetch failed:", error_9.message);
|
|
1418
2002
|
return [3 /*break*/, 8];
|
|
1419
2003
|
case 8:
|
|
1420
2004
|
_b = 0, specialCaips_1 = specialCaips;
|
|
@@ -1431,7 +2015,7 @@ var get_batch_prices_by_caip = function (caips_1) {
|
|
|
1431
2015
|
results[caip] = price;
|
|
1432
2016
|
return [3 /*break*/, 13];
|
|
1433
2017
|
case 12:
|
|
1434
|
-
|
|
2018
|
+
error_10 = _g.sent();
|
|
1435
2019
|
log.warn(tag, "Failed to fetch special case: ".concat(caip));
|
|
1436
2020
|
return [3 /*break*/, 13];
|
|
1437
2021
|
case 13:
|
|
@@ -1456,7 +2040,7 @@ var get_batch_prices_by_caip = function (caips_1) {
|
|
|
1456
2040
|
_d[_e] = _g.sent();
|
|
1457
2041
|
return [3 /*break*/, 19];
|
|
1458
2042
|
case 18:
|
|
1459
|
-
|
|
2043
|
+
error_11 = _g.sent();
|
|
1460
2044
|
log.warn(tag, "Failed to fetch fallback price for ".concat(caip));
|
|
1461
2045
|
results[caip] = returnSource ? { price: 0, source: 'none' } : 0;
|
|
1462
2046
|
return [3 /*break*/, 19];
|
|
@@ -1470,3 +2054,58 @@ var get_batch_prices_by_caip = function (caips_1) {
|
|
|
1470
2054
|
});
|
|
1471
2055
|
});
|
|
1472
2056
|
};
|
|
2057
|
+
/**
|
|
2058
|
+
* Initialize token buckets and metrics logging
|
|
2059
|
+
* Call this on module load
|
|
2060
|
+
*/
|
|
2061
|
+
function initializeTokenBuckets() {
|
|
2062
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
2063
|
+
var tag, mongo, db, error_12;
|
|
2064
|
+
return __generator(this, function (_a) {
|
|
2065
|
+
switch (_a.label) {
|
|
2066
|
+
case 0:
|
|
2067
|
+
tag = TAG + ' | initializeTokenBuckets | ';
|
|
2068
|
+
// Prevent double initialization
|
|
2069
|
+
if (isInitializing || tokenBucketManager) {
|
|
2070
|
+
log.debug(tag, 'Token buckets already initialized or initializing');
|
|
2071
|
+
return [2 /*return*/];
|
|
2072
|
+
}
|
|
2073
|
+
isInitializing = true;
|
|
2074
|
+
_a.label = 1;
|
|
2075
|
+
case 1:
|
|
2076
|
+
_a.trys.push([1, 3, 4, 5]);
|
|
2077
|
+
log.info(tag, 'Initializing token bucket system...');
|
|
2078
|
+
mongo = require('@pioneer-platform/default-mongo').mongo;
|
|
2079
|
+
db = mongo.db('pioneer');
|
|
2080
|
+
// Initialize metrics logger first
|
|
2081
|
+
metricsLogger = new metrics_logger_1.MetricsLogger(db);
|
|
2082
|
+
log.info(tag, '✅ MetricsLogger initialized');
|
|
2083
|
+
// Initialize token bucket manager
|
|
2084
|
+
tokenBucketManager = new token_bucket_manager_1.TokenBucketManager(db);
|
|
2085
|
+
return [4 /*yield*/, tokenBucketManager.initialize()];
|
|
2086
|
+
case 2:
|
|
2087
|
+
_a.sent();
|
|
2088
|
+
log.info(tag, '✅ TokenBucketManager initialized');
|
|
2089
|
+
// Schedule daily refill at midnight UTC
|
|
2090
|
+
(0, refill_scheduler_1.scheduleDailyRefill)(tokenBucketManager, metricsLogger);
|
|
2091
|
+
log.info(tag, '✅ Daily refill scheduler started');
|
|
2092
|
+
log.info(tag, '🎉 Token bucket system fully initialized!');
|
|
2093
|
+
return [3 /*break*/, 5];
|
|
2094
|
+
case 3:
|
|
2095
|
+
error_12 = _a.sent();
|
|
2096
|
+
log.error(tag, 'Failed to initialize token buckets:', error_12);
|
|
2097
|
+
return [3 /*break*/, 5];
|
|
2098
|
+
case 4:
|
|
2099
|
+
isInitializing = false;
|
|
2100
|
+
return [7 /*endfinally*/];
|
|
2101
|
+
case 5: return [2 /*return*/];
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
// Call on module load (with delay to ensure MongoDB is ready)
|
|
2107
|
+
setTimeout(function () {
|
|
2108
|
+
initializeTokenBuckets().catch(function (err) {
|
|
2109
|
+
log.error(TAG, 'Token bucket initialization failed:', err);
|
|
2110
|
+
});
|
|
2111
|
+
}, 5000); // 5 second delay to let MongoDB connect
|