@pioneer-platform/markets 8.11.23 → 8.11.24

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.
@@ -1,2 +1 @@
1
-
2
- $ tsc -p .
1
+ $ tsc -p .
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @pioneer-platform/markets
2
2
 
3
+ ## 8.11.24
4
+
5
+ ### Patch Changes
6
+
7
+ - cache work
8
+ - Updated dependencies
9
+ - @pioneer-platform/pro-token@0.8.2
10
+ - @pioneer-platform/pioneer-discovery@8.11.24
11
+
3
12
  ## 8.11.23
4
13
 
5
14
  ### Patch Changes
package/lib/index.js CHANGED
@@ -114,26 +114,16 @@ var GLOBAL_RATES_CMC;
114
114
  // Tracks in-flight API requests to prevent duplicate calls for same asset
115
115
  var pendingPriceRequests = new Map();
116
116
  var pendingPriceRequestsWithSource = new Map();
117
- // FIX: Rate limiters for external APIs
118
- // CoinGecko free tier: 50 calls/minute = 1.2s between calls (we use 1.5s for safety)
119
- var coingeckoLimiter = new Bottleneck({
120
- minTime: 1500, // 1.5 seconds between calls (40 calls/minute)
121
- maxConcurrent: 1
122
- });
123
- // CoinMarketCap: Varies by plan, using conservative limits
124
- // Basic plan: 333 calls/day 1 call every 4 minutes, but allow bursts
125
- var coinmarketcapLimiter = new Bottleneck({
126
- reservoir: 10, // Allow 10 calls initially
127
- reservoirRefreshAmount: 10,
128
- reservoirRefreshInterval: 60 * 1000, // Refresh 10 calls every minute
129
- minTime: 2000, // 2 seconds between calls
130
- maxConcurrent: 1
131
- });
132
- // CoinCap API 3.0: Similar to CoinGecko
133
- var coincapLimiter = new Bottleneck({
134
- minTime: 1500, // 1.5 seconds between calls
135
- maxConcurrent: 1
136
- });
117
+ // Cache of unpriceable tokens (scam/spam tokens not in any price feed)
118
+ // Using in-memory Map for instant lookups (no Redis timeout issues)
119
+ // Also persisted to Redis for cross-process sharing and persistence
120
+ var unpriceableTokensCache = new Map();
121
+ var UNPRICEABLE_TOKEN_PREFIX = 'unpriceable_token:';
122
+ var UNPRICEABLE_TOKEN_TTL = 7 * 24 * 60 * 60; // 7 days
123
+ var UNPRICEABLE_TOKEN_TTL_MS = UNPRICEABLE_TOKEN_TTL * 1000;
124
+ // NOTE: Rate limiting is now enforced at the worker level (pioneer-server)
125
+ // using a Redis mutex to ensure ONLY ONE market API call at a time across all processes
126
+ // The Bottleneck limiters below are kept as a safety net for direct module usage
137
127
  module.exports = {
138
128
  init: function (settings) {
139
129
  if (settings === null || settings === void 0 ? void 0 : settings.apiKey) {
@@ -718,14 +708,74 @@ var caip_to_identifiers = function (caip) {
718
708
  coincapSymbol: symbol.toLowerCase()
719
709
  };
720
710
  };
711
+ /**
712
+ * Check if token is marked as unpriceable (scam/spam token)
713
+ * Uses in-memory cache first for instant lookups (no Redis timeout issues)
714
+ * Returns true if cached as unpriceable, false otherwise
715
+ */
716
+ var is_unpriceable_token = function (caip) {
717
+ var tag = TAG + ' | is_unpriceable_token | ';
718
+ // Check in-memory cache first (instant, no timeout issues)
719
+ var cached = unpriceableTokensCache.get(caip);
720
+ if (cached) {
721
+ // Check if still valid (7 days)
722
+ var age = Date.now() - cached.markedAt;
723
+ if (age < UNPRICEABLE_TOKEN_TTL_MS) {
724
+ log.debug(tag, "\uD83D\uDEA8 Token is cached as unpriceable (maybeScam), skipping: ".concat(caip));
725
+ return true;
726
+ }
727
+ else {
728
+ // Expired, remove from cache
729
+ unpriceableTokensCache.delete(caip);
730
+ return false;
731
+ }
732
+ }
733
+ return false;
734
+ };
735
+ /**
736
+ * Cache token as unpriceable (not found in any of our 3 paid APIs)
737
+ * Stores in-memory for instant lookups + Redis for persistence
738
+ * This prevents wasting API calls on scam/spam tokens
739
+ */
740
+ var cache_unpriceable_token = function (caip) {
741
+ return __awaiter(this, void 0, void 0, function () {
742
+ var tag, key;
743
+ return __generator(this, function (_a) {
744
+ tag = TAG + ' | cache_unpriceable_token | ';
745
+ // Store in-memory for instant lookups
746
+ unpriceableTokensCache.set(caip, {
747
+ markedAt: Date.now(),
748
+ maybeScam: true
749
+ });
750
+ // Also persist to Redis for cross-process sharing (but don't wait/block)
751
+ try {
752
+ key = UNPRICEABLE_TOKEN_PREFIX + caip;
753
+ redis.setex(key, UNPRICEABLE_TOKEN_TTL, JSON.stringify({
754
+ caip: caip,
755
+ markedAt: new Date().toISOString(),
756
+ reason: 'not_found_in_paid_apis',
757
+ maybeScam: true
758
+ })).catch(function (err) {
759
+ // Ignore Redis errors - in-memory cache is primary
760
+ log.debug(tag, "Redis error (non-blocking): ".concat(err.message));
761
+ });
762
+ log.info(tag, "\u2705 Cached token as unpriceable for 7 days: ".concat(caip));
763
+ }
764
+ catch (error) {
765
+ // If Redis fails, don't block - in-memory cache is already set
766
+ log.debug(tag, "Redis error caching unpriceable token (non-critical): ".concat(error));
767
+ }
768
+ return [2 /*return*/];
769
+ });
770
+ });
771
+ };
721
772
  /**
722
773
  * Get price from CoinGecko by coin ID
723
774
  * Now with rate limiting to prevent API spam
724
775
  */
725
776
  var get_price_from_coingecko = function (coingeckoId) {
726
777
  return __awaiter(this, void 0, void 0, function () {
727
- var tag, error_1, status_3;
728
- var _this = this;
778
+ var tag, url, headers, response, price, error_1, status_3;
729
779
  var _a;
730
780
  return __generator(this, function (_b) {
731
781
  switch (_b.label) {
@@ -734,29 +784,22 @@ var get_price_from_coingecko = function (coingeckoId) {
734
784
  _b.label = 1;
735
785
  case 1:
736
786
  _b.trys.push([1, 3, , 4]);
737
- return [4 /*yield*/, coingeckoLimiter.schedule(function () { return __awaiter(_this, void 0, void 0, function () {
738
- var url, response, price;
739
- return __generator(this, function (_a) {
740
- switch (_a.label) {
741
- case 0:
742
- url = "".concat(URL_COINGECKO, "simple/price?ids=").concat(coingeckoId, "&vs_currencies=usd");
743
- log.debug(tag, "Fetching from CoinGecko: ".concat(url));
744
- return [4 /*yield*/, axios.get(url)];
745
- case 1:
746
- response = _a.sent();
747
- if (response.data && response.data[coingeckoId] && response.data[coingeckoId].usd) {
748
- price = parseFloat(response.data[coingeckoId].usd);
749
- log.debug(tag, "\u2705 CoinGecko price for ".concat(coingeckoId, ": $").concat(price));
750
- return [2 /*return*/, price];
751
- }
752
- log.debug(tag, "No price data from CoinGecko for ".concat(coingeckoId));
753
- return [2 /*return*/, 0];
754
- }
755
- });
756
- }); })];
757
- case 2:
758
- // Apply rate limiting
759
- return [2 /*return*/, _b.sent()];
787
+ url = "".concat(URL_COINGECKO, "simple/price?ids=").concat(coingeckoId, "&vs_currencies=usd");
788
+ log.debug(tag, "Fetching from CoinGecko: ".concat(url));
789
+ headers = {};
790
+ if (COINGECKO_API_KEY) {
791
+ headers['x-cg-pro-api-key'] = COINGECKO_API_KEY;
792
+ }
793
+ return [4 /*yield*/, axios.get(url, { headers: headers })];
794
+ case 2:
795
+ response = _b.sent();
796
+ if (response.data && response.data[coingeckoId] && response.data[coingeckoId].usd) {
797
+ price = parseFloat(response.data[coingeckoId].usd);
798
+ log.debug(tag, "\u2705 CoinGecko price for ".concat(coingeckoId, ": $").concat(price));
799
+ return [2 /*return*/, price];
800
+ }
801
+ log.debug(tag, "No price data from CoinGecko for ".concat(coingeckoId));
802
+ return [2 /*return*/, 0];
760
803
  case 3:
761
804
  error_1 = _b.sent();
762
805
  status_3 = ((_a = error_1.response) === null || _a === void 0 ? void 0 : _a.status) || error_1.status;
@@ -773,69 +816,105 @@ var get_price_from_coingecko = function (coingeckoId) {
773
816
  });
774
817
  };
775
818
  /**
776
- * Get price from CoinMarketCap by symbol
819
+ * Get price from CoinGecko by ERC20 contract address
820
+ * Fallback for tokens without CoinGecko ID mapping
777
821
  * Now with rate limiting to prevent API spam
778
822
  */
779
- var get_price_from_coinmarketcap = function (symbol) {
780
- return __awaiter(this, void 0, void 0, function () {
781
- var tag, error_2, status_4;
782
- var _this = this;
823
+ var get_price_from_coingecko_contract = function (contractAddress_1) {
824
+ return __awaiter(this, arguments, void 0, function (contractAddress, chainId) {
825
+ var tag, url, headers, response, contractKey, price, error_2, status_4;
783
826
  var _a;
827
+ if (chainId === void 0) { chainId = 'ethereum'; }
784
828
  return __generator(this, function (_b) {
785
829
  switch (_b.label) {
786
830
  case 0:
787
- tag = TAG + ' | get_price_from_coinmarketcap | ';
788
- if (!CMC_PRO_API_KEY) {
789
- log.debug(tag, 'CMC_PRO_API_KEY not set, skipping CoinMarketCap');
790
- return [2 /*return*/, 0];
791
- }
831
+ tag = TAG + ' | get_price_from_coingecko_contract | ';
792
832
  _b.label = 1;
793
833
  case 1:
794
834
  _b.trys.push([1, 3, , 4]);
795
- return [4 /*yield*/, coinmarketcapLimiter.schedule(function () { return __awaiter(_this, void 0, void 0, function () {
796
- var url, response, coinData, priceData, price;
797
- var _a, _b;
798
- return __generator(this, function (_c) {
799
- switch (_c.label) {
800
- case 0:
801
- url = "".concat(URL_COINMARKETCAP, "cryptocurrency/quotes/latest?symbol=").concat(symbol, "&convert=USD");
802
- log.debug(tag, "Fetching from CoinMarketCap: ".concat(url));
803
- return [4 /*yield*/, axios.get(url, {
804
- headers: {
805
- 'X-CMC_PRO_API_KEY': CMC_PRO_API_KEY,
806
- 'Accept': 'application/json'
807
- }
808
- })];
809
- case 1:
810
- response = _c.sent();
811
- if (response.data && response.data.data && response.data.data[symbol]) {
812
- coinData = response.data.data[symbol];
813
- priceData = (_b = (_a = coinData.quote) === null || _a === void 0 ? void 0 : _a.USD) === null || _b === void 0 ? void 0 : _b.price;
814
- if (!priceData || isNaN(parseFloat(priceData))) {
815
- log.warn(tag, "\u26A0\uFE0F CoinMarketCap returned invalid price for ".concat(symbol));
816
- log.warn(tag, "Symbol: ".concat(coinData.symbol, ", Slug: ").concat(coinData.slug, ", Quote keys:"), Object.keys(coinData.quote || {}));
817
- return [2 /*return*/, 0];
818
- }
819
- price = parseFloat(priceData);
820
- log.debug(tag, "\u2705 CoinMarketCap price for ".concat(symbol, ": $").concat(price));
821
- return [2 /*return*/, price];
822
- }
823
- log.debug(tag, "No price data from CoinMarketCap for ".concat(symbol));
824
- return [2 /*return*/, 0];
825
- }
826
- });
827
- }); })];
828
- case 2:
829
- // Apply rate limiting
830
- return [2 /*return*/, _b.sent()];
835
+ url = "".concat(URL_COINGECKO, "simple/token_price/").concat(chainId, "?contract_addresses=").concat(contractAddress, "&vs_currencies=usd");
836
+ log.debug(tag, "Fetching from CoinGecko contract endpoint: ".concat(url));
837
+ headers = {};
838
+ if (COINGECKO_API_KEY) {
839
+ headers['x-cg-pro-api-key'] = COINGECKO_API_KEY;
840
+ }
841
+ return [4 /*yield*/, axios.get(url, { headers: headers })];
842
+ case 2:
843
+ response = _b.sent();
844
+ contractKey = contractAddress.toLowerCase();
845
+ if (response.data && response.data[contractKey] && response.data[contractKey].usd) {
846
+ price = parseFloat(response.data[contractKey].usd);
847
+ log.debug(tag, "\u2705 CoinGecko contract price for ".concat(contractAddress, ": $").concat(price));
848
+ return [2 /*return*/, price];
849
+ }
850
+ log.debug(tag, "No price data from CoinGecko for contract ".concat(contractAddress));
851
+ return [2 /*return*/, 0];
831
852
  case 3:
832
853
  error_2 = _b.sent();
833
854
  status_4 = ((_a = error_2.response) === null || _a === void 0 ? void 0 : _a.status) || error_2.status;
834
855
  if (status_4 === 429 || status_4 === 403) {
835
- log.warn(tag, "CoinMarketCap rate limit (".concat(status_4, ") for ").concat(symbol));
856
+ log.warn(tag, "CoinGecko rate limit (".concat(status_4, ") for contract ").concat(contractAddress));
836
857
  }
837
858
  else {
838
- log.debug(tag, "CoinMarketCap error for ".concat(symbol, ": ").concat(error_2.message));
859
+ log.debug(tag, "CoinGecko contract error for ".concat(contractAddress, ": ").concat(error_2.message));
860
+ }
861
+ return [2 /*return*/, 0];
862
+ case 4: return [2 /*return*/];
863
+ }
864
+ });
865
+ });
866
+ };
867
+ /**
868
+ * Get price from CoinMarketCap by symbol
869
+ * Now with rate limiting to prevent API spam
870
+ */
871
+ var get_price_from_coinmarketcap = function (symbol) {
872
+ return __awaiter(this, void 0, void 0, function () {
873
+ var tag, url, response, coinData, priceData, price, error_3, status_5;
874
+ var _a, _b, _c;
875
+ return __generator(this, function (_d) {
876
+ switch (_d.label) {
877
+ case 0:
878
+ tag = TAG + ' | get_price_from_coinmarketcap | ';
879
+ if (!CMC_PRO_API_KEY) {
880
+ log.debug(tag, 'CMC_PRO_API_KEY not set, skipping CoinMarketCap');
881
+ return [2 /*return*/, 0];
882
+ }
883
+ _d.label = 1;
884
+ case 1:
885
+ _d.trys.push([1, 3, , 4]);
886
+ url = "".concat(URL_COINMARKETCAP, "cryptocurrency/quotes/latest?symbol=").concat(symbol, "&convert=USD");
887
+ log.debug(tag, "Fetching from CoinMarketCap: ".concat(url));
888
+ return [4 /*yield*/, axios.get(url, {
889
+ headers: {
890
+ 'X-CMC_PRO_API_KEY': CMC_PRO_API_KEY,
891
+ 'Accept': 'application/json'
892
+ }
893
+ })];
894
+ case 2:
895
+ response = _d.sent();
896
+ if (response.data && response.data.data && response.data.data[symbol]) {
897
+ coinData = response.data.data[symbol];
898
+ priceData = (_b = (_a = coinData.quote) === null || _a === void 0 ? void 0 : _a.USD) === null || _b === void 0 ? void 0 : _b.price;
899
+ if (!priceData || isNaN(parseFloat(priceData))) {
900
+ log.warn(tag, "\u26A0\uFE0F CoinMarketCap returned invalid price for ".concat(symbol));
901
+ log.warn(tag, "Symbol: ".concat(coinData.symbol, ", Slug: ").concat(coinData.slug, ", Quote keys:"), Object.keys(coinData.quote || {}));
902
+ return [2 /*return*/, 0];
903
+ }
904
+ price = parseFloat(priceData);
905
+ log.debug(tag, "\u2705 CoinMarketCap price for ".concat(symbol, ": $").concat(price));
906
+ return [2 /*return*/, price];
907
+ }
908
+ log.debug(tag, "No price data from CoinMarketCap for ".concat(symbol));
909
+ return [2 /*return*/, 0];
910
+ case 3:
911
+ error_3 = _d.sent();
912
+ status_5 = ((_c = error_3.response) === null || _c === void 0 ? void 0 : _c.status) || error_3.status;
913
+ if (status_5 === 429 || status_5 === 403) {
914
+ log.warn(tag, "CoinMarketCap rate limit (".concat(status_5, ") for ").concat(symbol));
915
+ }
916
+ else {
917
+ log.debug(tag, "CoinMarketCap error for ".concat(symbol, ": ").concat(error_3.message));
839
918
  }
840
919
  return [2 /*return*/, 0];
841
920
  case 4: return [2 /*return*/];
@@ -849,8 +928,7 @@ var get_price_from_coinmarketcap = function (symbol) {
849
928
  */
850
929
  var get_price_from_coincap = function (symbol) {
851
930
  return __awaiter(this, void 0, void 0, function () {
852
- var tag, error_3, status_5;
853
- var _this = this;
931
+ var tag, url, response, price, error_4, status_6;
854
932
  var _a;
855
933
  return __generator(this, function (_b) {
856
934
  switch (_b.label) {
@@ -863,42 +941,31 @@ var get_price_from_coincap = function (symbol) {
863
941
  _b.label = 1;
864
942
  case 1:
865
943
  _b.trys.push([1, 3, , 4]);
866
- return [4 /*yield*/, coincapLimiter.schedule(function () { return __awaiter(_this, void 0, void 0, function () {
867
- var url, response, price;
868
- return __generator(this, function (_a) {
869
- switch (_a.label) {
870
- case 0:
871
- url = "".concat(URL_COINCAP, "assets/").concat(symbol);
872
- log.debug(tag, "Fetching from CoinCap: ".concat(url));
873
- return [4 /*yield*/, axios.get(url, {
874
- headers: {
875
- 'Accept': 'application/json',
876
- 'Authorization': "Bearer ".concat(COINCAP_API_KEY)
877
- }
878
- })];
879
- case 1:
880
- response = _a.sent();
881
- if (response.data && response.data.data && response.data.data.priceUsd) {
882
- price = parseFloat(response.data.data.priceUsd);
883
- log.debug(tag, "\u2705 CoinCap price for ".concat(symbol, ": $").concat(price));
884
- return [2 /*return*/, price];
885
- }
886
- log.debug(tag, "No price data from CoinCap for ".concat(symbol));
887
- return [2 /*return*/, 0];
888
- }
889
- });
890
- }); })];
891
- case 2:
892
- // Apply rate limiting
893
- return [2 /*return*/, _b.sent()];
944
+ url = "".concat(URL_COINCAP, "assets/").concat(symbol);
945
+ log.debug(tag, "Fetching from CoinCap: ".concat(url));
946
+ return [4 /*yield*/, axios.get(url, {
947
+ headers: {
948
+ 'Accept': 'application/json',
949
+ 'Authorization': "Bearer ".concat(COINCAP_API_KEY)
950
+ }
951
+ })];
952
+ case 2:
953
+ response = _b.sent();
954
+ if (response.data && response.data.data && response.data.data.priceUsd) {
955
+ price = parseFloat(response.data.data.priceUsd);
956
+ log.debug(tag, "\u2705 CoinCap price for ".concat(symbol, ": $").concat(price));
957
+ return [2 /*return*/, price];
958
+ }
959
+ log.debug(tag, "No price data from CoinCap for ".concat(symbol));
960
+ return [2 /*return*/, 0];
894
961
  case 3:
895
- error_3 = _b.sent();
896
- status_5 = ((_a = error_3.response) === null || _a === void 0 ? void 0 : _a.status) || error_3.status;
897
- if (status_5 === 429 || status_5 === 403) {
898
- log.warn(tag, "CoinCap rate limit (".concat(status_5, ") for ").concat(symbol));
962
+ error_4 = _b.sent();
963
+ status_6 = ((_a = error_4.response) === null || _a === void 0 ? void 0 : _a.status) || error_4.status;
964
+ if (status_6 === 429 || status_6 === 403) {
965
+ log.warn(tag, "CoinCap rate limit (".concat(status_6, ") for ").concat(symbol));
899
966
  }
900
967
  else {
901
- log.debug(tag, "CoinCap error for ".concat(symbol, ": ").concat(error_3.message));
968
+ log.debug(tag, "CoinCap error for ".concat(symbol, ": ").concat(error_4.message));
902
969
  }
903
970
  return [2 /*return*/, 0];
904
971
  case 4: return [2 /*return*/];
@@ -913,7 +980,7 @@ var get_price_from_coincap = function (symbol) {
913
980
  */
914
981
  var get_price_from_mayascan = function () {
915
982
  return __awaiter(this, arguments, void 0, function (asset) {
916
- var tag, url, response, price, error_4;
983
+ var tag, url, response, price, error_5;
917
984
  if (asset === void 0) { asset = 'maya'; }
918
985
  return __generator(this, function (_a) {
919
986
  switch (_a.label) {
@@ -943,8 +1010,8 @@ var get_price_from_mayascan = function () {
943
1010
  log.debug(tag, "No ".concat(asset.toUpperCase(), " price data from MayaScan"));
944
1011
  return [2 /*return*/, 0];
945
1012
  case 3:
946
- error_4 = _a.sent();
947
- log.debug(tag, "MayaScan error: ".concat(error_4.message));
1013
+ error_5 = _a.sent();
1014
+ log.debug(tag, "MayaScan error: ".concat(error_5.message));
948
1015
  return [2 /*return*/, 0];
949
1016
  case 4: return [2 /*return*/];
950
1017
  }
@@ -985,13 +1052,18 @@ var get_asset_price_by_caip = function (caip_2) {
985
1052
  */
986
1053
  var get_asset_price_by_caip_internal = function (caip_2) {
987
1054
  return __awaiter(this, arguments, void 0, function (caip, returnSource) {
988
- var tag, mayaPrice, isCacao, identifiers, price, cacaoPrice;
1055
+ var tag, mayaPrice, isCacao, identifiers, price, contractAddress, chainIdMatch, chainId, chainMap, platformName, asset, symbol, cacaoPrice;
989
1056
  if (returnSource === void 0) { returnSource = false; }
990
1057
  return __generator(this, function (_a) {
991
1058
  switch (_a.label) {
992
1059
  case 0:
993
1060
  tag = TAG + ' | get_asset_price_by_caip_internal | ';
994
1061
  log.debug(tag, "Fetching price for: ".concat(caip));
1062
+ // EARLY EXIT: Check if token is cached as unpriceable (scam/spam token)
1063
+ // This is instant (in-memory check, no Redis timeout issues)
1064
+ if (is_unpriceable_token(caip)) {
1065
+ return [2 /*return*/, returnSource ? { price: 0, source: 'unpriceable', maybeScam: true } : 0];
1066
+ }
995
1067
  if (!(caip === 'cosmos:mayachain-mainnet-v1/denom:maya' || caip.toLowerCase().includes('denom:maya'))) return [3 /*break*/, 2];
996
1068
  log.debug(tag, 'MAYA token detected, using MayaScan API');
997
1069
  return [4 /*yield*/, get_price_from_mayascan('maya')];
@@ -1005,13 +1077,11 @@ var get_asset_price_by_caip_internal = function (caip_2) {
1005
1077
  case 2:
1006
1078
  isCacao = caip === 'cosmos:mayachain-mainnet-v1/slip44:931';
1007
1079
  identifiers = caip_to_identifiers(caip);
1008
- if (!identifiers) {
1009
- log.warn(tag, "No identifier mapping found for CAIP: ".concat(caip));
1010
- return [2 /*return*/, returnSource ? { price: 0, source: 'none' } : 0];
1011
- }
1080
+ price = 0;
1081
+ if (!identifiers) return [3 /*break*/, 4];
1012
1082
  log.debug(tag, "Identifiers for ".concat(caip, ":"), identifiers);
1013
- // Try CoinGecko first (free, no API key needed, most comprehensive)
1014
- console.log("\uD83E\uDD8E Trying CoinGecko for: ".concat(identifiers.coingeckoId));
1083
+ // Try CoinGecko with mapped ID
1084
+ console.log("\uD83E\uDD8E Trying CoinGecko with mapped ID: ".concat(identifiers.coingeckoId));
1015
1085
  return [4 /*yield*/, get_price_from_coingecko(identifiers.coingeckoId)];
1016
1086
  case 3:
1017
1087
  price = _a.sent();
@@ -1019,34 +1089,118 @@ var get_asset_price_by_caip_internal = function (caip_2) {
1019
1089
  if (price > 0) {
1020
1090
  return [2 /*return*/, returnSource ? { price: price, source: 'coingecko' } : price];
1021
1091
  }
1022
- // Try CoinMarketCap second (requires API key, very comprehensive)
1023
- console.log("\uD83D\uDCCA CoinGecko failed (price=$0), trying CoinMarketCap for: ".concat(identifiers.cmcSymbol));
1024
- return [4 /*yield*/, get_price_from_coinmarketcap(identifiers.cmcSymbol)];
1092
+ return [3 /*break*/, 5];
1025
1093
  case 4:
1094
+ log.debug(tag, "No identifier mapping found for CAIP: ".concat(caip));
1095
+ _a.label = 5;
1096
+ case 5:
1097
+ if (!caip.includes('/erc20:0x')) return [3 /*break*/, 13];
1098
+ contractAddress = caip.split('/erc20:')[1];
1099
+ chainIdMatch = caip.match(/eip155:(\d+)/);
1100
+ if (!(contractAddress && chainIdMatch)) return [3 /*break*/, 7];
1101
+ chainId = chainIdMatch[1];
1102
+ chainMap = {
1103
+ '1': 'ethereum',
1104
+ '137': 'polygon-pos',
1105
+ '56': 'binance-smart-chain',
1106
+ '43114': 'avalanche',
1107
+ '250': 'fantom',
1108
+ '42161': 'arbitrum-one',
1109
+ '10': 'optimistic-ethereum',
1110
+ '8453': 'base'
1111
+ };
1112
+ platformName = chainMap[chainId] || 'ethereum';
1113
+ console.log("\uD83D\uDD04 Trying CoinGecko contract lookup: ".concat(contractAddress, " on ").concat(platformName));
1114
+ return [4 /*yield*/, get_price_from_coingecko_contract(contractAddress, platformName)];
1115
+ case 6:
1116
+ price = _a.sent();
1117
+ console.log("\uD83D\uDD04 CoinGecko contract returned price: $".concat(price));
1118
+ if (price > 0) {
1119
+ return [2 /*return*/, returnSource ? { price: price, source: 'coingecko-contract' } : price];
1120
+ }
1121
+ _a.label = 7;
1122
+ case 7:
1123
+ if (!(price === 0)) return [3 /*break*/, 13];
1124
+ 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*/, 11];
1126
+ symbol = asset.symbol.toUpperCase();
1127
+ log.info(tag, "\uD83D\uDD04 Contract lookup failed, falling back to symbol-based APIs for: ".concat(symbol));
1128
+ // Try CoinMarketCap
1129
+ console.log("\uD83D\uDCCA Trying CoinMarketCap for ERC20 token: ".concat(symbol));
1130
+ return [4 /*yield*/, get_price_from_coinmarketcap(symbol)];
1131
+ case 8:
1026
1132
  price = _a.sent();
1027
1133
  console.log("\uD83D\uDCCA CoinMarketCap returned price: $".concat(price));
1028
1134
  if (price > 0) {
1029
1135
  return [2 /*return*/, returnSource ? { price: price, source: 'coinmarketcap' } : price];
1030
1136
  }
1031
- // Try CoinCap last (requires API key, currently rate limited)
1032
- console.log("\uD83D\uDCB0 CoinMarketCap failed (price=$0), trying CoinCap for: ".concat(identifiers.coincapSymbol));
1137
+ // Try CoinCap
1138
+ console.log("\uD83D\uDCB0 Trying CoinCap for ERC20 token: ".concat(symbol.toLowerCase()));
1139
+ return [4 /*yield*/, get_price_from_coincap(symbol.toLowerCase())];
1140
+ case 9:
1141
+ price = _a.sent();
1142
+ console.log("\uD83D\uDCB0 CoinCap returned price: $".concat(price));
1143
+ if (price > 0) {
1144
+ return [2 /*return*/, returnSource ? { price: price, source: 'coincap' } : price];
1145
+ }
1146
+ // All 3 paid APIs failed - mark as unpriceable (likely scam/spam token)
1147
+ log.warn(tag, "\u274C Token not found in any of 3 paid APIs (CoinGecko, CMC, CoinCap): ".concat(caip));
1148
+ log.warn(tag, "\uD83D\uDEA8 Marking as maybeScam - will not retry pricing for this token");
1149
+ // Cache the failure so we never try again
1150
+ return [4 /*yield*/, cache_unpriceable_token(caip)];
1151
+ case 10:
1152
+ // Cache the failure so we never try again
1153
+ _a.sent();
1154
+ return [2 /*return*/, returnSource ? { price: 0, source: 'unpriceable', maybeScam: true } : 0];
1155
+ case 11:
1156
+ // Token not in our discovery database - can't get symbol for API calls
1157
+ log.warn(tag, "\u274C Token not in discovery database, cannot lookup symbol: ".concat(caip));
1158
+ log.warn(tag, "\uD83D\uDEA8 Marking as maybeScam - likely user-added spam token");
1159
+ // Cache the failure so we never try again
1160
+ return [4 /*yield*/, cache_unpriceable_token(caip)];
1161
+ case 12:
1162
+ // Cache the failure so we never try again
1163
+ _a.sent();
1164
+ return [2 /*return*/, returnSource ? { price: 0, source: 'unpriceable', maybeScam: true } : 0];
1165
+ case 13:
1166
+ if (!!identifiers) return [3 /*break*/, 15];
1167
+ log.warn(tag, "No way to lookup price for CAIP: ".concat(caip, " (no mapping, not ERC20, or all lookups failed)"));
1168
+ log.warn(tag, "\uD83D\uDEA8 Marking as maybeScam - token has no price source");
1169
+ // Cache the failure so we never try again
1170
+ return [4 /*yield*/, cache_unpriceable_token(caip)];
1171
+ case 14:
1172
+ // Cache the failure so we never try again
1173
+ _a.sent();
1174
+ return [2 /*return*/, returnSource ? { price: 0, source: 'unpriceable', maybeScam: true } : 0];
1175
+ case 15:
1176
+ // STEP 4: Try CoinMarketCap by symbol (requires API key)
1177
+ console.log("\uD83D\uDCCA Trying CoinMarketCap for: ".concat(identifiers.cmcSymbol));
1178
+ return [4 /*yield*/, get_price_from_coinmarketcap(identifiers.cmcSymbol)];
1179
+ case 16:
1180
+ price = _a.sent();
1181
+ console.log("\uD83D\uDCCA CoinMarketCap returned price: $".concat(price));
1182
+ if (price > 0) {
1183
+ return [2 /*return*/, returnSource ? { price: price, source: 'coinmarketcap' } : price];
1184
+ }
1185
+ // STEP 5: Try CoinCap by symbol (last resort)
1186
+ console.log("\uD83D\uDCB0 Trying CoinCap for: ".concat(identifiers.coincapSymbol));
1033
1187
  return [4 /*yield*/, get_price_from_coincap(identifiers.coincapSymbol)];
1034
- case 5:
1188
+ case 17:
1035
1189
  price = _a.sent();
1036
1190
  console.log("\uD83D\uDCB0 CoinCap returned price: $".concat(price));
1037
1191
  if (price > 0) {
1038
1192
  return [2 /*return*/, returnSource ? { price: price, source: 'coincap' } : price];
1039
1193
  }
1040
- if (!isCacao) return [3 /*break*/, 7];
1194
+ if (!isCacao) return [3 /*break*/, 19];
1041
1195
  log.debug(tag, '🏔️ All pricing APIs failed for CACAO, trying MayaScan as final fallback');
1042
1196
  return [4 /*yield*/, get_price_from_mayascan('cacao')];
1043
- case 6:
1197
+ case 18:
1044
1198
  cacaoPrice = _a.sent();
1045
1199
  if (cacaoPrice > 0) {
1046
1200
  return [2 /*return*/, returnSource ? { price: cacaoPrice, source: 'mayascan' } : cacaoPrice];
1047
1201
  }
1048
- _a.label = 7;
1049
- case 7:
1202
+ _a.label = 19;
1203
+ case 19:
1050
1204
  log.warn(tag, "\u274C No price found from any API for: ".concat(caip));
1051
1205
  return [2 /*return*/, returnSource ? { price: 0, source: 'none' } : 0];
1052
1206
  }
@@ -1192,7 +1346,7 @@ var get_price = function (asset) {
1192
1346
  */
1193
1347
  var get_batch_prices_by_caip = function (caips_1) {
1194
1348
  return __awaiter(this, arguments, void 0, function (caips, returnSource) {
1195
- var tag, results, caipToIdMap, specialCaips, _i, caips_2, caip, identifiers, coingeckoIds, batchSize, i, batch, idsParam, url, response, _a, batch_1, coingeckoId, mapping, price, error_5, _b, specialCaips_1, caip, price, error_6, missingCaips, _c, missingCaips_1, caip, _d, _e, error_7;
1349
+ var tag, results, caipToIdMap, specialCaips, _i, caips_2, caip, identifiers, coingeckoIds, batchSize, i, batch, idsParam, url, response, _a, batch_1, coingeckoId, mapping, price, error_6, _b, specialCaips_1, caip, price, error_7, missingCaips, _c, missingCaips_1, caip, _d, _e, error_8;
1196
1350
  var _f;
1197
1351
  if (returnSource === void 0) { returnSource = false; }
1198
1352
  return __generator(this, function (_g) {
@@ -1259,8 +1413,8 @@ var get_batch_prices_by_caip = function (caips_1) {
1259
1413
  log.info(tag, "\u2705 Batch fetched ".concat(Object.keys(results).length, " prices from CoinGecko"));
1260
1414
  return [3 /*break*/, 8];
1261
1415
  case 7:
1262
- error_5 = _g.sent();
1263
- log.error(tag, "CoinGecko batch fetch failed:", error_5.message);
1416
+ error_6 = _g.sent();
1417
+ log.error(tag, "CoinGecko batch fetch failed:", error_6.message);
1264
1418
  return [3 /*break*/, 8];
1265
1419
  case 8:
1266
1420
  _b = 0, specialCaips_1 = specialCaips;
@@ -1277,7 +1431,7 @@ var get_batch_prices_by_caip = function (caips_1) {
1277
1431
  results[caip] = price;
1278
1432
  return [3 /*break*/, 13];
1279
1433
  case 12:
1280
- error_6 = _g.sent();
1434
+ error_7 = _g.sent();
1281
1435
  log.warn(tag, "Failed to fetch special case: ".concat(caip));
1282
1436
  return [3 /*break*/, 13];
1283
1437
  case 13:
@@ -1302,7 +1456,7 @@ var get_batch_prices_by_caip = function (caips_1) {
1302
1456
  _d[_e] = _g.sent();
1303
1457
  return [3 /*break*/, 19];
1304
1458
  case 18:
1305
- error_7 = _g.sent();
1459
+ error_8 = _g.sent();
1306
1460
  log.warn(tag, "Failed to fetch fallback price for ".concat(caip));
1307
1461
  results[caip] = returnSource ? { price: 0, source: 'none' } : 0;
1308
1462
  return [3 /*break*/, 19];
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@pioneer-platform/markets",
3
- "version": "8.11.23",
3
+ "version": "8.11.24",
4
4
  "main": "./lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
6
  "dependencies": {
7
7
  "@pioneer-platform/default-redis": "^8.11.7",
8
8
  "@pioneer-platform/loggerdog": "^8.11.0",
9
9
  "@pioneer-platform/pioneer-coins": "^9.11.0",
10
- "@pioneer-platform/pioneer-discovery": "^8.11.23",
10
+ "@pioneer-platform/pioneer-discovery": "^8.11.24",
11
11
  "@pioneer-platform/pioneer-types": "^8.11.0",
12
- "@pioneer-platform/pro-token": "^0.8.1",
12
+ "@pioneer-platform/pro-token": "^0.8.2",
13
13
  "@shapeshiftoss/caip": "^9.0.0-alpha.0",
14
14
  "axios": "^1.6.0",
15
15
  "axios-retry": "^3.2.0",