@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.
- package/.turbo/turbo-build.log +1 -2
- package/CHANGELOG.md +9 -0
- package/lib/index.js +307 -153
- package/package.json +3 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
[0m[2m[35m$[0m [2m[1mtsc -p .[0m
|
|
1
|
+
$ tsc -p .
|
package/CHANGELOG.md
CHANGED
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
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
|
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
|
|
780
|
-
return __awaiter(this,
|
|
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 + ' |
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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, "
|
|
856
|
+
log.warn(tag, "CoinGecko rate limit (".concat(status_4, ") for contract ").concat(contractAddress));
|
|
836
857
|
}
|
|
837
858
|
else {
|
|
838
|
-
log.debug(tag, "
|
|
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,
|
|
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
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
896
|
-
|
|
897
|
-
if (
|
|
898
|
-
log.warn(tag, "CoinCap rate limit (".concat(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
947
|
-
log.debug(tag, "MayaScan error: ".concat(
|
|
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
|
-
|
|
1009
|
-
|
|
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
|
|
1014
|
-
console.log("\uD83E\uDD8E Trying CoinGecko
|
|
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
|
-
|
|
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
|
|
1032
|
-
console.log("\uD83D\uDCB0
|
|
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
|
|
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*/,
|
|
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
|
|
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 =
|
|
1049
|
-
case
|
|
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,
|
|
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
|
-
|
|
1263
|
-
log.error(tag, "CoinGecko batch fetch failed:",
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
10
|
+
"@pioneer-platform/pioneer-discovery": "^8.11.24",
|
|
11
11
|
"@pioneer-platform/pioneer-types": "^8.11.0",
|
|
12
|
-
"@pioneer-platform/pro-token": "^0.8.
|
|
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",
|