diff --git a/README.md b/README.md index c5fed43..0a390c9 100644 --- a/README.md +++ b/README.md @@ -34,19 +34,19 @@ npm install npm start ``` -## 🔀 Источник баланса (Rabby / DeBank) +## 🔀 Источник баланса (Rabby) -Балансы по умолчанию берутся из **Rabby API** (`api.rabby.io`): итог кошелька -приходит готовым агрегатом в одном запросе `total_balance`, поэтому ручного +Балансы берутся из **Rabby API** (`api.rabby.io`): итог кошелька приходит +готовым агрегатом в одном запросе `total_balance`, поэтому ручного суммирования нет — это устраняет «фантомные» балансы (когда под нагрузкой -DeBank отдавал портфель чужого адреса и итог раздувался). - -Переключить источник можно переменной окружения (без пересборки): - -```bash -BALANCE_SOURCE=rabby npm start # по умолчанию — авторитетный агрегат -BALANCE_SOURCE=debank npm start # легаси-fallback (kill switch) -``` +DeBank отдавал портфель чужого адреса и итог раздувался). Ветка DeBank +удалена (как в github.com/privatekey7/DeBankChecker, PR #5). + +Заголовки запросов повторяют клиент Rabby байт-в-байт (сверено с HAR +расширения): lowercase-подписные заголовки, `x-api-time` = время выдачи +ключа, `x-version: 0.94.2`, браузерные `sec-fetch-*`/`accept-language`. +Отклонение карается фейковым 429 с пустым телом — разбор инцидента: +`docs/incident-429-antibot.md` в репозитории DeBankChecker. Тесты бэкенда: ```bash diff --git a/backend/dist/config.d.ts b/backend/dist/config.d.ts index 484ff31..e9c2678 100644 --- a/backend/dist/config.d.ts +++ b/backend/dist/config.d.ts @@ -1,5 +1,5 @@ /** - * Конфигурация источников баланса. + * Конфигурация источника баланса. * * Исторически балансы брались из DeBank API, где итог считался ВРУЧНУЮ * (`tokens + protocols` из двух отдельных запросов). Под нагрузкой DeBank-edge @@ -8,16 +8,10 @@ * запросом `/v1/user/total_balance`, поэтому ручного суммирования нет и * корневой сценарий фантома исчезает. * - * Переключатель `BALANCE_SOURCE` позволяет мгновенно откатиться на DeBank - * (feature flag / kill switch), не трогая код — как `BALANCE_SOURCE` в - * github.com/privatekey7/DeBankChecker. + * Ветка DeBank удалена (как в github.com/privatekey7/DeBankChecker, PR #5): + * итог и раньше брался только из авторитетного агрегата, так что функционально + * ничего не потеряно. */ -export type BalanceSource = 'rabby' | 'debank'; -/** - * Активный источник баланса. Можно переопределить переменной окружения - * `BALANCE_SOURCE=debank` без пересборки. - */ -export declare const BALANCE_SOURCE: BalanceSource; /** Настройки корроборации (защита от фантомных балансов). */ export type CorroborationConfig = { /** Включена ли корроборация независимыми выборками. */ @@ -32,19 +26,25 @@ export type CorroborationConfig = { absTol: number; }; /** - * Для Rabby источник авторитетный (готовый агрегат в одном ответе), поэтому - * `minAgree = 1` — лишних запросов не делаем. Для DeBank оставляем прежнюю - * корроборацию (2 согласованных выборки), т.к. там итог собирается вручную. + * Источник (Rabby) авторитетный — готовый агрегат в одном ответе, поэтому + * `minAgree = 1`: лишних запросов корроборация не делает, но механика + * остаётся как страховка. */ -export declare const CORROBORATION: Record; +export declare const CORROBORATION: CorroborationConfig; /** Константы Rabby API (проверены воспроизведением подписи из HAR веб-версии). */ export declare const RABBY: { readonly apiBase: "https://api.rabby.io"; /** Начальный x-api-key из HAR; сервер ротирует через `x-set-api-key`. */ readonly apiKeyInit: "7cee6f31-6611-4821-beb8-6ca9e29ed965"; - /** Версия клиента Rabby, под которую записан HAR. */ - readonly clientVersion: "0.94.1"; - /** Префикс строки подписи (у DeBank — `debank-api`). */ + /** + * Время выдачи init-ключа (из HAR веб-клиента). Отправляется как x-api-time: + * сервер ожидает время ВЫДАЧИ ключа, а не время запроса — вместе с кейсингом + * заголовков это сверено с HAR (иначе анти-бот отвечает фейковым 429). + */ + readonly apiKeyInitTime: 1762656362; + /** Версия клиента Rabby (из HAR расширения), под которую записана схема. */ + readonly clientVersion: "0.94.2"; + /** Префикс строки подписи (у DeBank был `debank-api`). */ readonly signPrefix: "rabby-api"; /** Брать только проверенные (core) токены — как галка в UI Rabby. */ readonly isCore: true; diff --git a/backend/dist/config.d.ts.map b/backend/dist/config.d.ts.map index 845dde5..7df7075 100644 --- a/backend/dist/config.d.ts.map +++ b/backend/dist/config.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,aACoD,CAAC;AAElF,6DAA6D;AAC7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,mBAAmB,CAepE,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,KAAK;;IAEhB,yEAAyE;;IAEzE,qDAAqD;;IAErD,wDAAwD;;IAExD,qEAAqE;;CAE7D,CAAC;AAEX,gDAAgD;AAChD,eAAO,MAAM,IAAI;IACf,2CAA2C;;IAE3C,kDAAkD;;CAE1C,CAAC"} \ No newline at end of file +{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,6DAA6D;AAC7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,mBAM3B,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,KAAK;;IAEhB,yEAAyE;;IAEzE;;;;OAIG;;IAEH,4EAA4E;;IAE5E,0DAA0D;;IAE1D,qEAAqE;;CAE7D,CAAC;AAEX,gDAAgD;AAChD,eAAO,MAAM,IAAI;IACf,2CAA2C;;IAE3C,kDAAkD;;CAE1C,CAAC"} \ No newline at end of file diff --git a/backend/dist/config.js b/backend/dist/config.js index ce5e485..75f74f6 100644 --- a/backend/dist/config.js +++ b/backend/dist/config.js @@ -1,6 +1,6 @@ "use strict"; /** - * Конфигурация источников баланса. + * Конфигурация источника баланса. * * Исторически балансы брались из DeBank API, где итог считался ВРУЧНУЮ * (`tokens + protocols` из двух отдельных запросов). Под нагрузкой DeBank-edge @@ -9,46 +9,38 @@ * запросом `/v1/user/total_balance`, поэтому ручного суммирования нет и * корневой сценарий фантома исчезает. * - * Переключатель `BALANCE_SOURCE` позволяет мгновенно откатиться на DeBank - * (feature flag / kill switch), не трогая код — как `BALANCE_SOURCE` в - * github.com/privatekey7/DeBankChecker. + * Ветка DeBank удалена (как в github.com/privatekey7/DeBankChecker, PR #5): + * итог и раньше брался только из авторитетного агрегата, так что функционально + * ничего не потеряно. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.HTTP = exports.RABBY = exports.CORROBORATION = exports.BALANCE_SOURCE = void 0; +exports.HTTP = exports.RABBY = exports.CORROBORATION = void 0; /** - * Активный источник баланса. Можно переопределить переменной окружения - * `BALANCE_SOURCE=debank` без пересборки. - */ -exports.BALANCE_SOURCE = process.env.BALANCE_SOURCE === 'debank' ? 'debank' : 'rabby'; -/** - * Для Rabby источник авторитетный (готовый агрегат в одном ответе), поэтому - * `minAgree = 1` — лишних запросов не делаем. Для DeBank оставляем прежнюю - * корроборацию (2 согласованных выборки), т.к. там итог собирается вручную. + * Источник (Rabby) авторитетный — готовый агрегат в одном ответе, поэтому + * `minAgree = 1`: лишних запросов корроборация не делает, но механика + * остаётся как страховка. */ exports.CORROBORATION = { - rabby: { - enabled: true, - minAgree: 1, - maxFetches: 8, - relTol: 0.02, - absTol: 1.0 - }, - debank: { - enabled: true, - minAgree: 2, - maxFetches: 8, - relTol: 0.02, - absTol: 1.0 - } + enabled: true, + minAgree: 1, + maxFetches: 8, + relTol: 0.02, + absTol: 1.0 }; /** Константы Rabby API (проверены воспроизведением подписи из HAR веб-версии). */ exports.RABBY = { apiBase: 'https://api.rabby.io', /** Начальный x-api-key из HAR; сервер ротирует через `x-set-api-key`. */ apiKeyInit: '7cee6f31-6611-4821-beb8-6ca9e29ed965', - /** Версия клиента Rabby, под которую записан HAR. */ - clientVersion: '0.94.1', - /** Префикс строки подписи (у DeBank — `debank-api`). */ + /** + * Время выдачи init-ключа (из HAR веб-клиента). Отправляется как x-api-time: + * сервер ожидает время ВЫДАЧИ ключа, а не время запроса — вместе с кейсингом + * заголовков это сверено с HAR (иначе анти-бот отвечает фейковым 429). + */ + apiKeyInitTime: 1762656362, + /** Версия клиента Rabby (из HAR расширения), под которую записана схема. */ + clientVersion: '0.94.2', + /** Префикс строки подписи (у DeBank был `debank-api`). */ signPrefix: 'rabby-api', /** Брать только проверенные (core) токены — как галка в UI Rabby. */ isCore: true diff --git a/backend/dist/config.js.map b/backend/dist/config.js.map index 23478d9..07c98dd 100644 --- a/backend/dist/config.js.map +++ b/backend/dist/config.js.map @@ -1 +1 @@ -{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAIH;;;GAGG;AACU,QAAA,cAAc,GACxB,OAAO,CAAC,GAAG,CAAC,cAAgC,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;AAgBlF;;;;GAIG;AACU,QAAA,aAAa,GAA+C;IACvE,KAAK,EAAE;QACL,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,CAAC;QACX,UAAU,EAAE,CAAC;QACb,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,GAAG;KACZ;IACD,MAAM,EAAE;QACN,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,CAAC;QACX,UAAU,EAAE,CAAC;QACb,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,GAAG;KACZ;CACF,CAAC;AAEF,kFAAkF;AACrE,QAAA,KAAK,GAAG;IACnB,OAAO,EAAE,sBAAsB;IAC/B,yEAAyE;IACzE,UAAU,EAAE,sCAAsC;IAClD,qDAAqD;IACrD,aAAa,EAAE,QAAQ;IACvB,wDAAwD;IACxD,UAAU,EAAE,WAAW;IACvB,qEAAqE;IACrE,MAAM,EAAE,IAAI;CACJ,CAAC;AAEX,gDAAgD;AACnC,QAAA,IAAI,GAAG;IAClB,2CAA2C;IAC3C,gBAAgB,EAAE,IAAI;IACtB,kDAAkD;IAClD,UAAU,EAAE,EAAE;CACN,CAAC"} \ No newline at end of file +{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAgBH;;;;GAIG;AACU,QAAA,aAAa,GAAwB;IAChD,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,CAAC;IACX,UAAU,EAAE,CAAC;IACb,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,GAAG;CACZ,CAAC;AAEF,kFAAkF;AACrE,QAAA,KAAK,GAAG;IACnB,OAAO,EAAE,sBAAsB;IAC/B,yEAAyE;IACzE,UAAU,EAAE,sCAAsC;IAClD;;;;OAIG;IACH,cAAc,EAAE,UAAU;IAC1B,4EAA4E;IAC5E,aAAa,EAAE,QAAQ;IACvB,0DAA0D;IAC1D,UAAU,EAAE,WAAW;IACvB,qEAAqE;IACrE,MAAM,EAAE,IAAI;CACJ,CAAC;AAEX,gDAAgD;AACnC,QAAA,IAAI,GAAG;IAClB,2CAA2C;IAC3C,gBAAgB,EAAE,IAAI;IACtB,kDAAkD;IAClD,UAAU,EAAE,EAAE;CACN,CAAC"} \ No newline at end of file diff --git a/backend/dist/server.d.ts.map b/backend/dist/server.d.ts.map index 1be7bbe..548235a 100644 --- a/backend/dist/server.d.ts.map +++ b/backend/dist/server.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAcvB,QAAA,MAAM,GAAG,6CAAY,CAAC;AA6mBtB,eAAe,GAAG,CAAC"} \ No newline at end of file +{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAavB,QAAA,MAAM,GAAG,6CAAY,CAAC;AA4mBtB,eAAe,GAAG,CAAC"} \ No newline at end of file diff --git a/backend/dist/server.js b/backend/dist/server.js index c0e51b9..113e8e2 100644 --- a/backend/dist/server.js +++ b/backend/dist/server.js @@ -10,7 +10,6 @@ const helmet_1 = __importDefault(require("helmet")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const balanceService_1 = require("./services/balanceService"); -const config_1 = require("./config"); const dataProcessor_1 = require("./services/dataProcessor"); const loggerService_1 = require("./services/loggerService"); const progressBar_1 = require("./utils/progressBar"); @@ -25,9 +24,8 @@ app.use((0, cors_1.default)({ })); app.use(express_1.default.json()); // Services -// Источник баланса выбирается флагом BALANCE_SOURCE (rabby|debank). Имя -// переменной оставлено прежним, чтобы не трогать остальной код сервера. -const debankService = (0, balanceService_1.createBalanceService)(); +// Источник баланса — Rabby API (ветка DeBank удалена, см. services/rabbyService.ts). +const balanceService = (0, balanceService_1.createBalanceService)(); const dataProcessor = new dataProcessor_1.DataProcessor(); const logger = loggerService_1.LoggerService.getInstance(); // Пути для файлов данных @@ -132,10 +130,10 @@ let processingProgress = null; // API Routes // Получить статус сервера app.get('/api/status', (req, res) => { - const proxyStatus = debankService.getProxyStatus(); - const proxyStats = debankService.getProxyStats(); + const proxyStatus = balanceService.getProxyStatus(); + const proxyStats = balanceService.getProxyStats(); const loggerStats = logger.getStats(); - const cacheStats = debankService.getCacheStats(); + const cacheStats = balanceService.getCacheStats(); // Получаем общее количество кошельков из файла const totalWallets = loadWalletsFromFile().length; // Проверяем наличие файлов данных @@ -384,7 +382,7 @@ app.delete('/api/logs', (req, res) => { // Очистить кэш app.delete('/api/cache', (req, res) => { try { - debankService.clearCache(); + balanceService.clearCache(); res.json({ message: 'Кэш очищен' }); } catch (error) { @@ -406,7 +404,7 @@ app.delete('/api/data', (req, res) => { logger.info('Файл состояния обработки удален'); } // Очищаем кэш - debankService.clearCache(); + balanceService.clearCache(); res.json({ message: 'Все данные и файлы очищены' }); } catch (error) { @@ -475,7 +473,7 @@ const processWallets = async (addresses) => { // берёт следующий — без батчей и head-of-line blocking (как ThreadPoolExecutor // в DeBankChecker). Ретраи с ротацией прокси уже внутри getWalletData, поэтому // здесь никаких задержек между попытками. - const proxyCount = debankService.getProxyStatus().total; + const proxyCount = balanceService.getProxyStatus().total; const MAX_WORKERS = 500; const PROXY_MULTIPLIER = 5; const concurrency = Math.max(1, Math.min(MAX_WORKERS, addresses.length, proxyCount > 0 ? proxyCount * PROXY_MULTIPLIER : addresses.length)); @@ -490,7 +488,7 @@ const processWallets = async (addresses) => { return; const address = addresses[index]; try { - const walletData = await debankService.getWalletData(address); + const walletData = await balanceService.getWalletData(address); if (walletData) { newWalletsData.push(walletData); logger.info(`Успешно получены данные для ${address}: $${walletData.totalValue.toFixed(2)}`); @@ -531,7 +529,7 @@ app.listen(PORT, async () => { logo_1.Logo.showStartupStatus(Number(PORT)); logger.info(`Backend сервер запущен на порту ${PORT}`); logger.info(`API доступен по адресу: http://localhost:${PORT}/api`); - logger.info(`Источник баланса: ${config_1.BALANCE_SOURCE.toUpperCase()}`); + logger.info(`Источник баланса: RABBY`); // Очищаем старые данные при запуске logger.info('Очистка старых данных при запуске...'); cleanupOldData(); diff --git a/backend/dist/server.js.map b/backend/dist/server.js.map index f37c92b..f21d59e 100644 --- a/backend/dist/server.js.map +++ b/backend/dist/server.js.map @@ -1 +1 @@ -{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/services/balanceService.d.ts b/backend/dist/services/balanceService.d.ts index 6a4da80..89a5468 100644 --- a/backend/dist/services/balanceService.d.ts +++ b/backend/dist/services/balanceService.d.ts @@ -1,9 +1,8 @@ import { WalletData } from '../types'; -import { BalanceSource } from '../config'; -import { DeBankService } from './debankService'; +import { RabbyService } from './rabbyService'; /** - * Публичный контракт источника баланса — общий для DeBank и Rabby. Сервер - * работает через этот интерфейс и не знает, какой источник активен. + * Публичный контракт источника баланса. Сервер работает через этот интерфейс + * и не знает деталей реализации. */ export interface BalanceService { getWalletData(walletAddress: string): Promise; @@ -11,7 +10,7 @@ export interface BalanceService { total: number; working: number; }; - getProxyStats(): ReturnType; + getProxyStats(): ReturnType; clearCache(): void; getCacheStats(): { totalEntries: number; @@ -20,9 +19,9 @@ export interface BalanceService { }; } /** - * Фабрика источника баланса по флагу `BALANCE_SOURCE`. - * `rabby` (по умолчанию) — авторитетный агрегат, фантом-баг устранён. - * `debank` — легаси-fallback (kill switch). + * Источник баланса — Rabby API (авторитетный агрегат, фантом-баг устранён). + * Ветка DeBank удалена (как в github.com/privatekey7/DeBankChecker, PR #5): + * итог и раньше брался только из total_usd_value. */ -export declare const createBalanceService: (source?: BalanceSource) => BalanceService; +export declare const createBalanceService: () => BalanceService; //# sourceMappingURL=balanceService.d.ts.map \ No newline at end of file diff --git a/backend/dist/services/balanceService.d.ts.map b/backend/dist/services/balanceService.d.ts.map index e23f946..f11b3b2 100644 --- a/backend/dist/services/balanceService.d.ts.map +++ b/backend/dist/services/balanceService.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"balanceService.d.ts","sourceRoot":"","sources":["../../src/services/balanceService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAiC,aAAa,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACjE,cAAc,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,aAAa,IAAI,UAAU,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC;IAC5D,UAAU,IAAI,IAAI,CAAC;IACnB,aAAa,IAAI;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CACvF;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAQ,aAA8B,KACrC,cAGwC,CAAC"} \ No newline at end of file +{"version":3,"file":"balanceService.d.ts","sourceRoot":"","sources":["../../src/services/balanceService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACjE,cAAc,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,aAAa,IAAI,UAAU,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC;IAC3D,UAAU,IAAI,IAAI,CAAC;IACnB,aAAa,IAAI;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CACvF;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAAO,cACP,CAAC"} \ No newline at end of file diff --git a/backend/dist/services/balanceService.js b/backend/dist/services/balanceService.js index 3255207..5e8d4da 100644 --- a/backend/dist/services/balanceService.js +++ b/backend/dist/services/balanceService.js @@ -2,15 +2,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.createBalanceService = void 0; const config_1 = require("../config"); -const debankService_1 = require("./debankService"); const rabbyService_1 = require("./rabbyService"); /** - * Фабрика источника баланса по флагу `BALANCE_SOURCE`. - * `rabby` (по умолчанию) — авторитетный агрегат, фантом-баг устранён. - * `debank` — легаси-fallback (kill switch). + * Источник баланса — Rabby API (авторитетный агрегат, фантом-баг устранён). + * Ветка DeBank удалена (как в github.com/privatekey7/DeBankChecker, PR #5): + * итог и раньше брался только из total_usd_value. */ -const createBalanceService = (source = config_1.BALANCE_SOURCE) => source === 'debank' - ? new debankService_1.DeBankService(config_1.CORROBORATION.debank) - : new rabbyService_1.RabbyService(config_1.CORROBORATION.rabby); +const createBalanceService = () => new rabbyService_1.RabbyService(config_1.CORROBORATION); exports.createBalanceService = createBalanceService; //# sourceMappingURL=balanceService.js.map \ No newline at end of file diff --git a/backend/dist/services/balanceService.js.map b/backend/dist/services/balanceService.js.map index 6e023e3..f0e502d 100644 --- a/backend/dist/services/balanceService.js.map +++ b/backend/dist/services/balanceService.js.map @@ -1 +1 @@ -{"version":3,"file":"balanceService.js","sourceRoot":"","sources":["../../src/services/balanceService.ts"],"names":[],"mappings":";;;AACA,sCAAyE;AACzE,mDAAgD;AAChD,iDAA8C;AAc9C;;;;GAIG;AACI,MAAM,oBAAoB,GAAG,CAClC,SAAwB,uBAAc,EACtB,EAAE,CAClB,MAAM,KAAK,QAAQ;IACjB,CAAC,CAAC,IAAI,6BAAa,CAAC,sBAAa,CAAC,MAAM,CAAC;IACzC,CAAC,CAAC,IAAI,2BAAY,CAAC,sBAAa,CAAC,KAAK,CAAC,CAAC;AAL/B,QAAA,oBAAoB,wBAKW"} \ No newline at end of file +{"version":3,"file":"balanceService.js","sourceRoot":"","sources":["../../src/services/balanceService.ts"],"names":[],"mappings":";;;AACA,sCAA0C;AAC1C,iDAA8C;AAc9C;;;;GAIG;AACI,MAAM,oBAAoB,GAAG,GAAmB,EAAE,CACvD,IAAI,2BAAY,CAAC,sBAAa,CAAC,CAAC;AADrB,QAAA,oBAAoB,wBACC"} \ No newline at end of file diff --git a/backend/dist/services/debankService.d.ts b/backend/dist/services/debankService.d.ts deleted file mode 100644 index 798711a..0000000 --- a/backend/dist/services/debankService.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { WalletData, ProxyConfig } from '../types'; -import { CorroborationConfig } from '../config'; -import { BaseBalanceService } from './baseBalanceService'; -/** - * Легаси-источник баланса на DeBank API. Итог собирается ВРУЧНУЮ из двух - * запросов (tokens + protocols) → под нагрузкой возможен фантом. Оставлен как - * fallback за флагом `BALANCE_SOURCE=debank`; актуальный источник — Rabby. - */ -export declare class DeBankService extends BaseBalanceService { - private requestTimeout; - constructor(corroboration: CorroborationConfig); - protected fetchWalletData: (walletAddress: string, proxy: ProxyConfig | null) => Promise; -} -//# sourceMappingURL=debankService.d.ts.map \ No newline at end of file diff --git a/backend/dist/services/debankService.js b/backend/dist/services/debankService.js deleted file mode 100644 index 111f62c..0000000 --- a/backend/dist/services/debankService.js +++ /dev/null @@ -1,121 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.DeBankService = void 0; -const axios_1 = __importDefault(require("axios")); -const crypto_1 = __importDefault(require("crypto")); -const https_proxy_agent_1 = require("https-proxy-agent"); -const socks_proxy_agent_1 = require("socks-proxy-agent"); -const config_1 = require("../config"); -const apiSigner_1 = require("./apiSigner"); -const walletBuilder_1 = require("./walletBuilder"); -const baseBalanceService_1 = require("./baseBalanceService"); -const API_BASE = 'https://api.debank.com'; -const API_KEY_INIT = '3b92c003-ddc1-4c2d-b36e-781838f362c5'; -const SIGN_PREFIX = 'debank-api'; -const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'; -const buildProxyUrl = (proxy) => { - const auth = proxy.username && proxy.password - ? `${encodeURIComponent(proxy.username)}:${encodeURIComponent(proxy.password)}@` - : ''; - return `${proxy.protocol}://${auth}${proxy.host}:${proxy.port}`; -}; -/** - * Лёгкий клиент DeBank API: подписанные HMAC-SHA256 запросы через прокси - * (портировано с github.com/privatekey7/DeBankChecker). - */ -class DeBankApiClient { - constructor(proxy, timeout) { - this.apiKey = API_KEY_INIT; - this.initTs = Math.floor(Date.now() / 1000); - this.randomId = crypto_1.default.randomBytes(16).toString('hex'); - this.buildHeaders = (params, method, path) => { - const sign = (0, apiSigner_1.signRequest)(SIGN_PREFIX, method, path, params); - const account = JSON.stringify({ - random_at: this.initTs, - random_id: this.randomId, - user_addr: null, - connected_addr: null - }); - return { - 'User-Agent': USER_AGENT, - Referer: 'https://debank.com/', - Origin: 'https://debank.com', - 'X-API-Key': this.apiKey, - 'X-API-Time': String(this.initTs), - 'x-api-ts': String(sign.ts), - 'x-api-nonce': sign.nonce, - 'x-api-ver': sign.version, - 'x-api-sign': sign.signature, - source: 'web', - account - }; - }; - this.get = async (path, params) => { - const headers = this.buildHeaders(params, 'GET', path); - const resp = await this.http.get(path, { params, headers }); - const newKey = resp.headers['x-set-api-key']; - if (newKey) { - this.apiKey = newKey; - } - const data = resp.data; - if (data && typeof data === 'object' && 'data' in data) { - return data.data; - } - return data; - }; - /** Кэшированный список токенов по всем сетям (один запрос). */ - this.getTokenBalances = async (address) => { - const result = await this.get('/token/cache_balance_list', { user_addr: address }); - return Array.isArray(result) ? result : []; - }; - /** DeFi протоколы с позициями. */ - this.getPortfolio = async (address) => { - const result = await this.get('/portfolio/project_list', { user_addr: address }); - return Array.isArray(result) ? result : []; - }; - let agent; - if (proxy) { - const url = buildProxyUrl(proxy); - agent = proxy.protocol.startsWith('socks') - ? new socks_proxy_agent_1.SocksProxyAgent(url) - : new https_proxy_agent_1.HttpsProxyAgent(url); - } - this.http = axios_1.default.create({ - baseURL: API_BASE, - timeout, - httpsAgent: agent, - httpAgent: agent, - proxy: false - }); - } -} -/** - * Легаси-источник баланса на DeBank API. Итог собирается ВРУЧНУЮ из двух - * запросов (tokens + protocols) → под нагрузкой возможен фантом. Оставлен как - * fallback за флагом `BALANCE_SOURCE=debank`; актуальный источник — Rabby. - */ -class DeBankService extends baseBalanceService_1.BaseBalanceService { - constructor(corroboration) { - super(corroboration); - this.requestTimeout = config_1.HTTP.requestTimeoutMs; - this.fetchWalletData = async (walletAddress, proxy) => { - const client = new DeBankApiClient(proxy, this.requestTimeout); - // Два запроса параллельно вместо загрузки страницы браузером - const [tokens, portfolio] = await Promise.all([ - client.getTokenBalances(walletAddress), - client.getPortfolio(walletAddress) - ]); - this.logger.addProcessingStep(walletAddress, `Получено токенов: ${tokens.length}, протоколов: ${portfolio.length}`); - // totalOverride не задан → итог = сумма tokens + protocols (прежнее поведение). - const walletData = (0, walletBuilder_1.buildWalletData)(walletAddress, tokens, portfolio); - this.logger.setRawData(walletAddress, { token_balance_list: tokens, portfolio_list: portfolio }); - this.logger.setProcessedData(walletAddress, walletData); - return walletData; - }; - } -} -exports.DeBankService = DeBankService; -//# sourceMappingURL=debankService.js.map \ No newline at end of file diff --git a/backend/dist/services/debankService.js.map b/backend/dist/services/debankService.js.map deleted file mode 100644 index c4968ef..0000000 --- a/backend/dist/services/debankService.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"debankService.js","sourceRoot":"","sources":["../../src/services/debankService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/services/rabbyService.d.ts b/backend/dist/services/rabbyService.d.ts index 3e9f7fc..c1c2884 100644 --- a/backend/dist/services/rabbyService.d.ts +++ b/backend/dist/services/rabbyService.d.ts @@ -1,18 +1,24 @@ import { WalletData, ProxyConfig } from '../types'; import { CorroborationConfig } from '../config'; import { BaseBalanceService } from './baseBalanceService'; +/** Сброс магазина ключа к init-значению (для тестов). */ +export declare const resetKeyState: () => void; /** * Лёгкий клиент Rabby API: подписанные HMAC-SHA256 запросы через прокси. * - * Отличия от DeBank-клиента (проверено HAR + Season12): - * - base URL `api.rabby.io`, префикс подписи `rabby-api`; - * - идентификация через `x-client: Rabby` + `x-version` (без `account`/`source`/Referer); - * - параметр адреса — `id` (lowercase); - * - начальный `x-api-key` — Rabby-ключ, ротируется через `x-set-api-key`. + * Заголовки идентификации должны ТОЧНО повторять клиент Rabby (сверено с HAR + * браузерного расширения; см. docs/incident-429-antibot.md в DeBankChecker): + * - подписные заголовки — в нижнем регистре (x-api-key, x-api-time, ...); + * - x-api-time — время ВЫДАЧИ текущего API-ключа, а не время запроса; + * - x-version — 0.94.2 (версия из HAR расширения); + * - браузерные заголовки (accept-language, dnt, priority, sec-fetch-*) + * досылаются поверх User-Agent. + * Анти-бот API на любое отклонение отвечает фейковым 429 с пустым телом — + * именно так душился /v1/user/token_list при верной подписи. */ export declare class RabbyApiClient { private apiKey; - private initTs; + private keyTime; private http; constructor(proxy: ProxyConfig | null, timeout: number); private buildHeaders; @@ -26,7 +32,14 @@ export declare class RabbyApiClient { total_usd_value: number; chain_list: any[]; }>; - /** Токены одной сети. `is_all=false` = только проверенные (core). */ + /** + * Токены кошелька по ВСЕМ сетям одним запросом (серверный кэш). + * Заменяет десятки запросов token_list (по одному на сеть) — расширение + * Rabby само использует этот эндпоинт для быстрой загрузки. Ответ — тот же + * формат токенов, фильтрация на стороне чекера. + */ + getCacheTokenList: (address: string) => Promise; + /** Токены одной сети (фолбэк при сбое cache_token_list). `is_all=false` = только core. */ getTokenList: (address: string, chainId: string) => Promise; /** DeFi-протоколы с позициями. Возвращает `{ apps, error_apps }`. */ getComplexAppList: (address: string) => Promise; @@ -41,6 +54,7 @@ export declare class RabbyService extends BaseBalanceService { private requestTimeout; constructor(corroboration: CorroborationConfig); protected fetchWalletData: (walletAddress: string, proxy: ProxyConfig | null) => Promise; + private fetchTokens; /** * Приводим Rabby-приложения к форме DeBank-протокола, ожидаемой билдером. * У Rabby нет сети на уровне протокола — берём её из первого токена позиции, diff --git a/backend/dist/services/rabbyService.d.ts.map b/backend/dist/services/rabbyService.d.ts.map index e88008d..bce741d 100644 --- a/backend/dist/services/rabbyService.d.ts.map +++ b/backend/dist/services/rabbyService.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"rabbyService.d.ts","sourceRoot":"","sources":["../../src/services/rabbyService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAe,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAa1D;;;;;;;;GAQG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,IAAI,CAAgB;gBAEhB,KAAK,EAAE,WAAW,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM;IAiBtD,OAAO,CAAC,YAAY,CAalB;IAEF,OAAO,CAAC,GAAG,CAcT;IAEF;;;;OAIG;IACI,eAAe,GACpB,SAAS,MAAM,KACd,OAAO,CAAC;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC,CASxD;IAEF,qEAAqE;IAC9D,YAAY,GAAU,SAAS,MAAM,EAAE,SAAS,MAAM,KAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAO5E;IAEF,qEAAqE;IAC9D,iBAAiB,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAGhE;CACH;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,kBAAkB;IAClD,OAAO,CAAC,cAAc,CAAyB;gBAEnC,aAAa,EAAE,mBAAmB;IAI9C,SAAS,CAAC,eAAe,GACvB,eAAe,MAAM,EACrB,OAAO,WAAW,GAAG,IAAI,KACxB,OAAO,CAAC,UAAU,CAAC,CAiCpB;IAEF;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAYrB;CACN"} \ No newline at end of file +{"version":3,"file":"rabbyService.d.ts","sourceRoot":"","sources":["../../src/services/rabbyService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAe,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAoC1D,yDAAyD;AACzD,eAAO,MAAM,aAAa,QAAO,IAGhC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAAgB;gBAEhB,KAAK,EAAE,WAAW,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM;IAoBtD,OAAO,CAAC,YAAY,CAuBlB;IAEF,OAAO,CAAC,GAAG,CAkCT;IAEF;;;;OAIG;IACI,eAAe,GACpB,SAAS,MAAM,KACd,OAAO,CAAC;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC,CASxD;IAEF;;;;;OAKG;IACI,iBAAiB,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAGhE;IAEF,0FAA0F;IACnF,YAAY,GAAU,SAAS,MAAM,EAAE,SAAS,MAAM,KAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAO5E;IAEF,qEAAqE;IAC9D,iBAAiB,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAGhE;CACH;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,kBAAkB;IAClD,OAAO,CAAC,cAAc,CAAyB;gBAEnC,aAAa,EAAE,mBAAmB;IAI9C,SAAS,CAAC,eAAe,GACvB,eAAe,MAAM,EACrB,OAAO,WAAW,GAAG,IAAI,KACxB,OAAO,CAAC,UAAU,CAAC,CAmCpB;IAEF,OAAO,CAAC,WAAW,CA2BjB;IAEF;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAYrB;CACN"} \ No newline at end of file diff --git a/backend/dist/services/rabbyService.js b/backend/dist/services/rabbyService.js index f50e767..e99b70d 100644 --- a/backend/dist/services/rabbyService.js +++ b/backend/dist/services/rabbyService.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RabbyService = exports.RabbyApiClient = void 0; +exports.RabbyService = exports.RabbyApiClient = exports.resetKeyState = void 0; const axios_1 = __importDefault(require("axios")); const https_proxy_agent_1 = require("https-proxy-agent"); const socks_proxy_agent_1 = require("socks-proxy-agent"); @@ -11,6 +11,9 @@ const config_1 = require("../config"); const apiSigner_1 = require("./apiSigner"); const walletBuilder_1 = require("./walletBuilder"); const baseBalanceService_1 = require("./baseBalanceService"); +// Node не умеет имперсонировать TLS-фингерпринт (в Python-эталоне это делает +// curl_cffi), поэтому UA задаём явно — «голый» axios без User-Agent палится +// сильнее всего. const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'; const buildProxyUrl = (proxy) => { const auth = proxy.username && proxy.password @@ -18,25 +21,51 @@ const buildProxyUrl = (proxy) => { : ''; return `${proxy.protocol}://${auth}${proxy.host}:${proxy.port}`; }; +const keyState = { key: config_1.RABBY.apiKeyInit, time: config_1.RABBY.apiKeyInitTime }; +const rotateKey = (newKey) => { + if (newKey && newKey !== keyState.key) { + keyState.key = newKey; + keyState.time = Math.floor(Date.now() / 1000); + } + return keyState.time; +}; +/** Сброс магазина ключа к init-значению (для тестов). */ +const resetKeyState = () => { + keyState.key = config_1.RABBY.apiKeyInit; + keyState.time = config_1.RABBY.apiKeyInitTime; +}; +exports.resetKeyState = resetKeyState; /** * Лёгкий клиент Rabby API: подписанные HMAC-SHA256 запросы через прокси. * - * Отличия от DeBank-клиента (проверено HAR + Season12): - * - base URL `api.rabby.io`, префикс подписи `rabby-api`; - * - идентификация через `x-client: Rabby` + `x-version` (без `account`/`source`/Referer); - * - параметр адреса — `id` (lowercase); - * - начальный `x-api-key` — Rabby-ключ, ротируется через `x-set-api-key`. + * Заголовки идентификации должны ТОЧНО повторять клиент Rabby (сверено с HAR + * браузерного расширения; см. docs/incident-429-antibot.md в DeBankChecker): + * - подписные заголовки — в нижнем регистре (x-api-key, x-api-time, ...); + * - x-api-time — время ВЫДАЧИ текущего API-ключа, а не время запроса; + * - x-version — 0.94.2 (версия из HAR расширения); + * - браузерные заголовки (accept-language, dnt, priority, sec-fetch-*) + * досылаются поверх User-Agent. + * Анти-бот API на любое отклонение отвечает фейковым 429 с пустым телом — + * именно так душился /v1/user/token_list при верной подписи. */ class RabbyApiClient { constructor(proxy, timeout) { - this.apiKey = config_1.RABBY.apiKeyInit; - this.initTs = Math.floor(Date.now() / 1000); this.buildHeaders = (params, method, path) => { const sign = (0, apiSigner_1.signRequest)(config_1.RABBY.signPrefix, method, path, params); + // Состав и кейсинг — строго по HAR расширения Rabby: отклонение карается + // фейковым 429 с пустым телом. return { 'User-Agent': USER_AGENT, - 'X-API-Key': this.apiKey, - 'X-API-Time': String(this.initTs), + accept: 'application/json, text/plain, */*', + 'accept-language': 'ru,ru-RU;q=0.9,en-US;q=0.8,en;q=0.7', + dnt: '1', + priority: 'u=1, i', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'none', + 'sec-fetch-storage-access': 'active', + 'x-api-key': this.apiKey, + 'x-api-time': String(this.keyTime), // время ВЫДАЧИ ключа 'x-api-ts': String(sign.ts), 'x-api-nonce': sign.nonce, 'x-api-ver': sign.version, @@ -47,13 +76,31 @@ class RabbyApiClient { }; this.get = async (path, params) => { const headers = this.buildHeaders(params, 'GET', path); - const resp = await this.http.get(path, { params, headers }); + let resp; + try { + resp = await this.http.get(path, { params, headers }); + } + catch (error) { + // Ротация ключа читается ДО выброса ошибки: сервер может выдать новый + // ключ вместе с 429/403 — раньше (raise_for_status до чтения заголовка + // в эталоне, axios-throw здесь) он терялся. + const newKey = error?.response?.headers?.['x-set-api-key']; + if (newKey) { + this.keyTime = rotateKey(newKey); + this.apiKey = newKey; + } + throw error; + } const newKey = resp.headers['x-set-api-key']; - if (newKey) { + if (newKey && newKey !== this.apiKey) { + this.keyTime = rotateKey(newKey); this.apiKey = newKey; } const data = resp.data; - if (data && typeof data === 'object' && 'data' in data) { + if (data && + typeof data === 'object' && + 'data' in data && + Object.keys(data).every(k => k === 'data' || k === 'error_code')) { return data.data; } return data; @@ -73,12 +120,22 @@ class RabbyApiClient { chain_list: Array.isArray(result?.chain_list) ? result.chain_list : [] }; }; - /** Токены одной сети. `is_all=false` = только проверенные (core). */ + /** + * Токены кошелька по ВСЕМ сетям одним запросом (серверный кэш). + * Заменяет десятки запросов token_list (по одному на сеть) — расширение + * Rabby само использует этот эндпоинт для быстрой загрузки. Ответ — тот же + * формат токенов, фильтрация на стороне чекера. + */ + this.getCacheTokenList = async (address) => { + const result = await this.get('/v1/user/cache_token_list', { id: address.toLowerCase() }); + return Array.isArray(result) ? result : []; + }; + /** Токены одной сети (фолбэк при сбое cache_token_list). `is_all=false` = только core. */ this.getTokenList = async (address, chainId) => { const result = await this.get('/v1/user/token_list', { id: address.toLowerCase(), chain_id: chainId, - is_all: 'false' + is_all: String(!config_1.RABBY.isCore) }); return Array.isArray(result) ? result : []; }; @@ -87,6 +144,8 @@ class RabbyApiClient { const result = await this.get('/v1/user/complex_app_list', { id: address.toLowerCase() }); return Array.isArray(result?.apps) ? result.apps : []; }; + this.apiKey = keyState.key; + this.keyTime = keyState.time; let agent; if (proxy) { const url = buildProxyUrl(proxy); @@ -118,15 +177,17 @@ class RabbyService extends baseBalanceService_1.BaseBalanceService { const client = new RabbyApiClient(proxy, this.requestTimeout); // 1. Авторитетный итог + сети (один запрос, is_core). const { total_usd_value, chain_list } = await client.getTotalBalance(walletAddress); - // 2. Детализация параллельно: токены по ненулевым сетям + DeFi-протоколы. const nonEmptyChains = chain_list .filter(c => (c?.usd_value || 0) > 0 && c?.id) .map(c => c.id); - const [tokenLists, apps] = await Promise.all([ - Promise.all(nonEmptyChains.map(chainId => client.getTokenList(walletAddress, chainId))), + // 2. Детализация параллельно: токены + DeFi-протоколы. + // Токены: все сети одним запросом (cache_token_list); при сбое — + // фолбэк на по-сетевой token_list. Пустой chain_list (пустой кошелёк) + // → запросы токенов не выполняются вовсе (так же поступает расширение). + const [tokens, apps] = await Promise.all([ + this.fetchTokens(client, walletAddress, nonEmptyChains), client.getComplexAppList(walletAddress) ]); - const tokens = tokenLists.flat(); const portfolio = this.mapAppsToPortfolio(apps); this.logger.addProcessingStep(walletAddress, `Rabby: итог $${total_usd_value.toFixed(2)}, сетей: ${nonEmptyChains.length}, токенов: ${tokens.length}, протоколов: ${portfolio.length}`); // Итог — из авторитетного агрегата (totalOverride), а НЕ сумма tokens+protocols. @@ -138,6 +199,27 @@ class RabbyService extends baseBalanceService_1.BaseBalanceService { this.logger.setProcessedData(walletAddress, walletData); return walletData; }; + this.fetchTokens = async (client, address, nonEmptyChains) => { + if (nonEmptyChains.length === 0) + return []; + let tokens; + try { + tokens = await client.getCacheTokenList(address); + } + catch { + tokens = []; + for (const chainId of nonEmptyChains) { + tokens = tokens.concat(await client.getTokenList(address, chainId)); + } + } + // cache_token_list отдаёт всё подряд — скам/несоверифицированные/ + // не-core фильтруем сами (token_list с is_all=false уже отфильтрован + // сервером, повторный фильтр безвреден). + return tokens.filter(t => t && + t.is_verified !== false && + !t.is_scam && + (!config_1.RABBY.isCore || t.is_core !== false)); + }; /** * Приводим Rabby-приложения к форме DeBank-протокола, ожидаемой билдером. * У Rabby нет сети на уровне протокола — берём её из первого токена позиции, diff --git a/backend/dist/services/rabbyService.js.map b/backend/dist/services/rabbyService.js.map index 16d7094..16d082f 100644 --- a/backend/dist/services/rabbyService.js.map +++ b/backend/dist/services/rabbyService.js.map @@ -1 +1 @@ -{"version":3,"file":"rabbyService.js","sourceRoot":"","sources":["../../src/services/rabbyService.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"rabbyService.js","sourceRoot":"","sources":["../../src/services/rabbyService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/types/index.d.ts b/backend/dist/types/index.d.ts index cac3331..2dc0f6c 100644 --- a/backend/dist/types/index.d.ts +++ b/backend/dist/types/index.d.ts @@ -41,12 +41,6 @@ export type ProxyConfig = { username?: string; password?: string; }; -export type DeBankApiResponse = { - user?: any; - token_balance_list?: any[]; - portfolio_list?: any[] | undefined; - total_net_curve?: any; -}; export type AggregatedData = { totalValue: number; totalChange24h: number; diff --git a/backend/dist/types/index.d.ts.map b/backend/dist/types/index.d.ts.map index ef9d63f..77d4f29 100644 --- a/backend/dist/types/index.d.ts.map +++ b/backend/dist/types/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,kBAAkB,CAAC,EAAE,GAAG,EAAE,CAAC;IAC3B,cAAc,CAAC,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC;IACnC,eAAe,CAAC,EAAE,GAAG,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB,CAAC"} \ No newline at end of file diff --git a/backend/scripts/smokeRabby.ts b/backend/scripts/smokeRabby.ts new file mode 100644 index 0000000..fc6ce8f --- /dev/null +++ b/backend/scripts/smokeRabby.ts @@ -0,0 +1,92 @@ +/** + * Live smoke-тест клиента Rabby (docs-аналог инцидента 429): один кошелёк, + * один прокси из proxy.txt. Проверяет, что заголовки проходят анти-бот + * (никаких фейковых 429) и что cache_token_list возвращает токены. + * + * Запуск из backend/: node dist/scripts/smokeRabby.js + */ +import fs from 'fs'; +import path from 'path'; +import { RabbyApiClient } from '../src/services/rabbyService'; +import { HTTP } from '../src/config'; + +const WALLETS = path.join(__dirname, '../../wallets.txt'); +const PROXIES = path.join(__dirname, '../../proxy.txt'); + +const parseProxyLine = (line: string) => { + // поддерживаем http://...@host:port, host:port:user:pass и host:port + let protocol = 'http'; + let rest = line; + if (line.includes('://')) { + [protocol, rest] = line.split('://'); + } + let username: string | undefined; + let password: string | undefined; + let hostPort = rest; + if (rest.includes('@')) { + const [auth, tail] = rest.split('@'); + [username, password] = auth.split(':'); + hostPort = tail; + } else if ((rest.match(/:/g) || []).length === 3) { + const parts = rest.split(':'); + username = parts[2]; + password = parts[3]; + hostPort = `${parts[0]}:${parts[1]}`; + } + const [host, port] = hostPort.split(':'); + return { + host, + port: parseInt(port), + protocol: protocol as any, + username, + password + }; +}; + +const run = async () => { + const address = fs + .readFileSync(WALLETS, 'utf8') + .split('\n') + .map(l => l.trim()) + .find(l => l.startsWith('0x'))!; + const proxyLines = fs + .readFileSync(PROXIES, 'utf8') + .split('\n') + .map(l => l.trim()) + .filter(l => l && !l.startsWith('#')); + + console.log(`Кошелёк: ${address}`); + + let lastErr: any = null; + for (const proxyLine of proxyLines.slice(0, 8)) { + try { + console.log(`Прокси: ${proxyLine.replace(/^[^@]*@/, '')} (${proxyLine.includes('@') ? '' : 'без '}auth)`); + + const client = new RabbyApiClient(parseProxyLine(proxyLine), 15000); + + let t = Date.now(); + const total = await client.getTotalBalance(address); + console.log(`total_balance: OK за ${((Date.now() - t) / 1000).toFixed(1)}s → $${total.total_usd_value}, сетей: ${total.chain_list.length}`); + + t = Date.now(); + const tokens = await client.getCacheTokenList(address); + console.log(`cache_token_list: OK за ${((Date.now() - t) / 1000).toFixed(1)}s → токенов: ${tokens.length}`); + + t = Date.now(); + const apps = await client.getComplexAppList(address); + console.log(`complex_app_list: OK за ${((Date.now() - t) / 1000).toFixed(1)}s → протоколов: ${apps.length}`); + + console.log('SMOKE OK: анти-бот пропустил все три запроса'); + return; + } catch (err: any) { + lastErr = err; + console.log(` → ${err?.response?.status ?? ''} ${err?.message ?? err}; пробуем следующий прокси`); + } + } + throw lastErr ?? new Error('нет прокси'); +}; + +run().catch(err => { + console.error('SMOKE FAIL:', err?.response?.status ?? '', err?.message ?? err); + process.exit(1); +}); diff --git a/backend/scripts/smokeService.ts b/backend/scripts/smokeService.ts new file mode 100644 index 0000000..d9b0847 --- /dev/null +++ b/backend/scripts/smokeService.ts @@ -0,0 +1,46 @@ +/** + * Live e2e-проверка полного пути RabbyService.getWalletData (первые N + * кошельков из wallets.txt, реальные прокси из proxy.txt). + * + * Запуск из backend/: npx ts-node scripts/smokeService.ts [N] + */ +import fs from 'fs'; +import path from 'path'; +import { RabbyService } from '../src/services/rabbyService'; +import { CORROBORATION } from '../src/config'; + +const run = async () => { + const n = parseInt(process.argv[2] || '5', 10); + const addresses = fs + .readFileSync(path.join(__dirname, '../../wallets.txt'), 'utf8') + .split('\n') + .map(l => l.trim()) + .filter(l => l.startsWith('0x')) + .slice(0, n); + + const service = new RabbyService(CORROBORATION); + + const t0 = Date.now(); + const results = await Promise.all( + addresses.map(async addr => { + const t = Date.now(); + try { + const wd = await service.getWalletData(addr); + return `${addr.slice(0, 10)}… ${wd ? `OK $${wd.totalValue.toFixed(2)} (${wd.tokens.length} токенов, ${wd.protocols.length} протоколов)` : 'NULL'}`; + } catch (e: any) { + return `${addr.slice(0, 10)}… THROW ${e?.message}`; + } finally { + void ((Date.now() - t) / 1000).toFixed(1); + } + }) + ); + const elapsed = ((Date.now() - t0) / 1000).toFixed(1); + + for (const r of results) console.log(r); + console.log(`\nE2E OK: ${addresses.length} кошельков за ${elapsed}s (${service.getProxyStatus().working}/${service.getProxyStatus().total} прокси рабочие)`); +}; + +run().catch(err => { + console.error('E2E FAIL:', err?.message ?? err); + process.exit(1); +}); diff --git a/backend/src/config.ts b/backend/src/config.ts index adcb38d..37ad861 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -1,5 +1,5 @@ /** - * Конфигурация источников баланса. + * Конфигурация источника баланса. * * Исторически балансы брались из DeBank API, где итог считался ВРУЧНУЮ * (`tokens + protocols` из двух отдельных запросов). Под нагрузкой DeBank-edge @@ -8,20 +8,11 @@ * запросом `/v1/user/total_balance`, поэтому ручного суммирования нет и * корневой сценарий фантома исчезает. * - * Переключатель `BALANCE_SOURCE` позволяет мгновенно откатиться на DeBank - * (feature flag / kill switch), не трогая код — как `BALANCE_SOURCE` в - * github.com/privatekey7/DeBankChecker. + * Ветка DeBank удалена (как в github.com/privatekey7/DeBankChecker, PR #5): + * итог и раньше брался только из авторитетного агрегата, так что функционально + * ничего не потеряно. */ -export type BalanceSource = 'rabby' | 'debank'; - -/** - * Активный источник баланса. Можно переопределить переменной окружения - * `BALANCE_SOURCE=debank` без пересборки. - */ -export const BALANCE_SOURCE: BalanceSource = - (process.env.BALANCE_SOURCE as BalanceSource) === 'debank' ? 'debank' : 'rabby'; - /** Настройки корроборации (защита от фантомных балансов). */ export type CorroborationConfig = { /** Включена ли корроборация независимыми выборками. */ @@ -37,25 +28,16 @@ export type CorroborationConfig = { }; /** - * Для Rabby источник авторитетный (готовый агрегат в одном ответе), поэтому - * `minAgree = 1` — лишних запросов не делаем. Для DeBank оставляем прежнюю - * корроборацию (2 согласованных выборки), т.к. там итог собирается вручную. + * Источник (Rabby) авторитетный — готовый агрегат в одном ответе, поэтому + * `minAgree = 1`: лишних запросов корроборация не делает, но механика + * остаётся как страховка. */ -export const CORROBORATION: Record = { - rabby: { - enabled: true, - minAgree: 1, - maxFetches: 8, - relTol: 0.02, - absTol: 1.0 - }, - debank: { - enabled: true, - minAgree: 2, - maxFetches: 8, - relTol: 0.02, - absTol: 1.0 - } +export const CORROBORATION: CorroborationConfig = { + enabled: true, + minAgree: 1, + maxFetches: 8, + relTol: 0.02, + absTol: 1.0 }; /** Константы Rabby API (проверены воспроизведением подписи из HAR веб-версии). */ @@ -63,9 +45,15 @@ export const RABBY = { apiBase: 'https://api.rabby.io', /** Начальный x-api-key из HAR; сервер ротирует через `x-set-api-key`. */ apiKeyInit: '7cee6f31-6611-4821-beb8-6ca9e29ed965', - /** Версия клиента Rabby, под которую записан HAR. */ - clientVersion: '0.94.1', - /** Префикс строки подписи (у DeBank — `debank-api`). */ + /** + * Время выдачи init-ключа (из HAR веб-клиента). Отправляется как x-api-time: + * сервер ожидает время ВЫДАЧИ ключа, а не время запроса — вместе с кейсингом + * заголовков это сверено с HAR (иначе анти-бот отвечает фейковым 429). + */ + apiKeyInitTime: 1762656362, + /** Версия клиента Rabby (из HAR расширения), под которую записана схема. */ + clientVersion: '0.94.2', + /** Префикс строки подписи (у DeBank был `debank-api`). */ signPrefix: 'rabby-api', /** Брать только проверенные (core) токены — как галка в UI Rabby. */ isCore: true diff --git a/backend/src/server.ts b/backend/src/server.ts index 0505c51..9b826da 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -5,7 +5,6 @@ import helmet from 'helmet'; import fs from 'fs'; import path from 'path'; import { createBalanceService } from './services/balanceService'; -import { BALANCE_SOURCE } from './config'; import { DataProcessor } from './services/dataProcessor'; import { WalletData } from './types'; import { LoggerService } from './services/loggerService'; @@ -24,9 +23,8 @@ app.use(cors({ app.use(express.json()); // Services -// Источник баланса выбирается флагом BALANCE_SOURCE (rabby|debank). Имя -// переменной оставлено прежним, чтобы не трогать остальной код сервера. -const debankService = createBalanceService(); +// Источник баланса — Rabby API (ветка DeBank удалена, см. services/rabbyService.ts). +const balanceService = createBalanceService(); const dataProcessor = new DataProcessor(); const logger = LoggerService.getInstance(); @@ -146,10 +144,10 @@ let processingProgress: { current: number; total: number } | null = null; // Получить статус сервера app.get('/api/status', (req, res) => { - const proxyStatus = debankService.getProxyStatus(); - const proxyStats = debankService.getProxyStats(); + const proxyStatus = balanceService.getProxyStatus(); + const proxyStats = balanceService.getProxyStats(); const loggerStats = logger.getStats(); - const cacheStats = debankService.getCacheStats(); + const cacheStats = balanceService.getCacheStats(); // Получаем общее количество кошельков из файла const totalWallets = loadWalletsFromFile().length; @@ -427,7 +425,7 @@ app.delete('/api/logs', (req, res) => { // Очистить кэш app.delete('/api/cache', (req, res) => { try { - debankService.clearCache(); + balanceService.clearCache(); res.json({ message: 'Кэш очищен' }); } catch (error) { logger.error('Ошибка при очистке кэша', error); @@ -452,7 +450,7 @@ app.delete('/api/data', (req, res) => { } // Очищаем кэш - debankService.clearCache(); + balanceService.clearCache(); res.json({ message: 'Все данные и файлы очищены' }); } catch (error) { @@ -532,7 +530,7 @@ const processWallets = async (addresses: string[]) => { // берёт следующий — без батчей и head-of-line blocking (как ThreadPoolExecutor // в DeBankChecker). Ретраи с ротацией прокси уже внутри getWalletData, поэтому // здесь никаких задержек между попытками. - const proxyCount = debankService.getProxyStatus().total; + const proxyCount = balanceService.getProxyStatus().total; const MAX_WORKERS = 500; const PROXY_MULTIPLIER = 5; const concurrency = Math.max( @@ -557,7 +555,7 @@ const processWallets = async (addresses: string[]) => { const address = addresses[index]; try { - const walletData = await debankService.getWalletData(address); + const walletData = await balanceService.getWalletData(address); if (walletData) { newWalletsData.push(walletData); logger.info(`Успешно получены данные для ${address}: $${walletData.totalValue.toFixed(2)}`); @@ -602,7 +600,7 @@ app.listen(PORT, async () => { logger.info(`Backend сервер запущен на порту ${PORT}`); logger.info(`API доступен по адресу: http://localhost:${PORT}/api`); - logger.info(`Источник баланса: ${BALANCE_SOURCE.toUpperCase()}`); + logger.info(`Источник баланса: RABBY`); // Очищаем старые данные при запуске logger.info('Очистка старых данных при запуске...'); diff --git a/backend/src/services/balanceService.ts b/backend/src/services/balanceService.ts index c72ba91..bb30243 100644 --- a/backend/src/services/balanceService.ts +++ b/backend/src/services/balanceService.ts @@ -1,28 +1,23 @@ import { WalletData } from '../types'; -import { BALANCE_SOURCE, CORROBORATION, BalanceSource } from '../config'; -import { DeBankService } from './debankService'; +import { CORROBORATION } from '../config'; import { RabbyService } from './rabbyService'; /** - * Публичный контракт источника баланса — общий для DeBank и Rabby. Сервер - * работает через этот интерфейс и не знает, какой источник активен. + * Публичный контракт источника баланса. Сервер работает через этот интерфейс + * и не знает деталей реализации. */ export interface BalanceService { getWalletData(walletAddress: string): Promise; getProxyStatus(): { total: number; working: number }; - getProxyStats(): ReturnType; + getProxyStats(): ReturnType; clearCache(): void; getCacheStats(): { totalEntries: number; validEntries: number; cacheTimeout: number }; } /** - * Фабрика источника баланса по флагу `BALANCE_SOURCE`. - * `rabby` (по умолчанию) — авторитетный агрегат, фантом-баг устранён. - * `debank` — легаси-fallback (kill switch). + * Источник баланса — Rabby API (авторитетный агрегат, фантом-баг устранён). + * Ветка DeBank удалена (как в github.com/privatekey7/DeBankChecker, PR #5): + * итог и раньше брался только из total_usd_value. */ -export const createBalanceService = ( - source: BalanceSource = BALANCE_SOURCE -): BalanceService => - source === 'debank' - ? new DeBankService(CORROBORATION.debank) - : new RabbyService(CORROBORATION.rabby); +export const createBalanceService = (): BalanceService => + new RabbyService(CORROBORATION); diff --git a/backend/src/services/debankService.ts b/backend/src/services/debankService.ts deleted file mode 100644 index f6b2a03..0000000 --- a/backend/src/services/debankService.ts +++ /dev/null @@ -1,140 +0,0 @@ -import axios, { AxiosInstance } from 'axios'; -import crypto from 'crypto'; -import { HttpsProxyAgent } from 'https-proxy-agent'; -import { SocksProxyAgent } from 'socks-proxy-agent'; -import { WalletData, ProxyConfig } from '../types'; -import { CorroborationConfig, HTTP } from '../config'; -import { signRequest } from './apiSigner'; -import { buildWalletData } from './walletBuilder'; -import { BaseBalanceService } from './baseBalanceService'; - -const API_BASE = 'https://api.debank.com'; -const API_KEY_INIT = '3b92c003-ddc1-4c2d-b36e-781838f362c5'; -const SIGN_PREFIX = 'debank-api'; -const USER_AGENT = - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'; - -const buildProxyUrl = (proxy: ProxyConfig): string => { - const auth = - proxy.username && proxy.password - ? `${encodeURIComponent(proxy.username)}:${encodeURIComponent(proxy.password)}@` - : ''; - return `${proxy.protocol}://${auth}${proxy.host}:${proxy.port}`; -}; - -/** - * Лёгкий клиент DeBank API: подписанные HMAC-SHA256 запросы через прокси - * (портировано с github.com/privatekey7/DeBankChecker). - */ -class DeBankApiClient { - private apiKey = API_KEY_INIT; - private initTs = Math.floor(Date.now() / 1000); - private randomId = crypto.randomBytes(16).toString('hex'); - private http: AxiosInstance; - - constructor(proxy: ProxyConfig | null, timeout: number) { - let agent; - if (proxy) { - const url = buildProxyUrl(proxy); - agent = proxy.protocol.startsWith('socks') - ? new SocksProxyAgent(url) - : new HttpsProxyAgent(url); - } - this.http = axios.create({ - baseURL: API_BASE, - timeout, - httpsAgent: agent, - httpAgent: agent, - proxy: false - }); - } - - private buildHeaders = (params: Record, method: string, path: string) => { - const sign = signRequest(SIGN_PREFIX, method, path, params); - const account = JSON.stringify({ - random_at: this.initTs, - random_id: this.randomId, - user_addr: null, - connected_addr: null - }); - return { - 'User-Agent': USER_AGENT, - Referer: 'https://debank.com/', - Origin: 'https://debank.com', - 'X-API-Key': this.apiKey, - 'X-API-Time': String(this.initTs), - 'x-api-ts': String(sign.ts), - 'x-api-nonce': sign.nonce, - 'x-api-ver': sign.version, - 'x-api-sign': sign.signature, - source: 'web', - account - }; - }; - - private get = async (path: string, params: Record): Promise => { - const headers = this.buildHeaders(params, 'GET', path); - const resp = await this.http.get(path, { params, headers }); - - const newKey = resp.headers['x-set-api-key']; - if (newKey) { - this.apiKey = newKey; - } - - const data = resp.data; - if (data && typeof data === 'object' && 'data' in data) { - return data.data; - } - return data; - }; - - /** Кэшированный список токенов по всем сетям (один запрос). */ - public getTokenBalances = async (address: string): Promise => { - const result = await this.get('/token/cache_balance_list', { user_addr: address }); - return Array.isArray(result) ? result : []; - }; - - /** DeFi протоколы с позициями. */ - public getPortfolio = async (address: string): Promise => { - const result = await this.get('/portfolio/project_list', { user_addr: address }); - return Array.isArray(result) ? result : []; - }; -} - -/** - * Легаси-источник баланса на DeBank API. Итог собирается ВРУЧНУЮ из двух - * запросов (tokens + protocols) → под нагрузкой возможен фантом. Оставлен как - * fallback за флагом `BALANCE_SOURCE=debank`; актуальный источник — Rabby. - */ -export class DeBankService extends BaseBalanceService { - private requestTimeout = HTTP.requestTimeoutMs; - - constructor(corroboration: CorroborationConfig) { - super(corroboration); - } - - protected fetchWalletData = async ( - walletAddress: string, - proxy: ProxyConfig | null - ): Promise => { - const client = new DeBankApiClient(proxy, this.requestTimeout); - - // Два запроса параллельно вместо загрузки страницы браузером - const [tokens, portfolio] = await Promise.all([ - client.getTokenBalances(walletAddress), - client.getPortfolio(walletAddress) - ]); - - this.logger.addProcessingStep( - walletAddress, - `Получено токенов: ${tokens.length}, протоколов: ${portfolio.length}` - ); - - // totalOverride не задан → итог = сумма tokens + protocols (прежнее поведение). - const walletData = buildWalletData(walletAddress, tokens, portfolio); - this.logger.setRawData(walletAddress, { token_balance_list: tokens, portfolio_list: portfolio }); - this.logger.setProcessedData(walletAddress, walletData); - - return walletData; - }; -} diff --git a/backend/src/services/rabbyService.ts b/backend/src/services/rabbyService.ts index e733a56..5c28c04 100644 --- a/backend/src/services/rabbyService.ts +++ b/backend/src/services/rabbyService.ts @@ -1,5 +1,4 @@ import axios, { AxiosInstance } from 'axios'; -import crypto from 'crypto'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { SocksProxyAgent } from 'socks-proxy-agent'; import { WalletData, ProxyConfig } from '../types'; @@ -8,6 +7,9 @@ import { signRequest } from './apiSigner'; import { buildWalletData } from './walletBuilder'; import { BaseBalanceService } from './baseBalanceService'; +// Node не умеет имперсонировать TLS-фингерпринт (в Python-эталоне это делает +// curl_cffi), поэтому UA задаём явно — «голый» axios без User-Agent палится +// сильнее всего. const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'; @@ -19,21 +21,54 @@ const buildProxyUrl = (proxy: ProxyConfig): string => { return `${proxy.protocol}://${auth}${proxy.host}:${proxy.port}`; }; +// Общий на процесс магазин ключа: снапшоты создают новый клиент на каждую +// попытку, а ротированный сервером ключ терять нельзя — иначе каждый клиент +// снова начнёт с init-ключа и его лимитов. Все клиенты продолжают с последнего +// выданного ключа; время выдачи нового ключа — момент ротации. Node +// однопоточен, поэтому обычного объекта достаточно (аналог _KEY_STATE в +// Python-эталоне). +interface KeyState { + key: string; + time: number; +} +const keyState: KeyState = { key: RABBY.apiKeyInit, time: RABBY.apiKeyInitTime }; + +const rotateKey = (newKey: string): number => { + if (newKey && newKey !== keyState.key) { + keyState.key = newKey; + keyState.time = Math.floor(Date.now() / 1000); + } + return keyState.time; +}; + +/** Сброс магазина ключа к init-значению (для тестов). */ +export const resetKeyState = (): void => { + keyState.key = RABBY.apiKeyInit; + keyState.time = RABBY.apiKeyInitTime; +}; + /** * Лёгкий клиент Rabby API: подписанные HMAC-SHA256 запросы через прокси. * - * Отличия от DeBank-клиента (проверено HAR + Season12): - * - base URL `api.rabby.io`, префикс подписи `rabby-api`; - * - идентификация через `x-client: Rabby` + `x-version` (без `account`/`source`/Referer); - * - параметр адреса — `id` (lowercase); - * - начальный `x-api-key` — Rabby-ключ, ротируется через `x-set-api-key`. + * Заголовки идентификации должны ТОЧНО повторять клиент Rabby (сверено с HAR + * браузерного расширения; см. docs/incident-429-antibot.md в DeBankChecker): + * - подписные заголовки — в нижнем регистре (x-api-key, x-api-time, ...); + * - x-api-time — время ВЫДАЧИ текущего API-ключа, а не время запроса; + * - x-version — 0.94.2 (версия из HAR расширения); + * - браузерные заголовки (accept-language, dnt, priority, sec-fetch-*) + * досылаются поверх User-Agent. + * Анти-бот API на любое отклонение отвечает фейковым 429 с пустым телом — + * именно так душился /v1/user/token_list при верной подписи. */ export class RabbyApiClient { - private apiKey = RABBY.apiKeyInit; - private initTs = Math.floor(Date.now() / 1000); + private apiKey: string; + private keyTime: number; private http: AxiosInstance; constructor(proxy: ProxyConfig | null, timeout: number) { + this.apiKey = keyState.key; + this.keyTime = keyState.time; + let agent; if (proxy) { const url = buildProxyUrl(proxy); @@ -52,10 +87,20 @@ export class RabbyApiClient { private buildHeaders = (params: Record, method: string, path: string) => { const sign = signRequest(RABBY.signPrefix, method, path, params); + // Состав и кейсинг — строго по HAR расширения Rabby: отклонение карается + // фейковым 429 с пустым телом. return { 'User-Agent': USER_AGENT, - 'X-API-Key': this.apiKey, - 'X-API-Time': String(this.initTs), + accept: 'application/json, text/plain, */*', + 'accept-language': 'ru,ru-RU;q=0.9,en-US;q=0.8,en;q=0.7', + dnt: '1', + priority: 'u=1, i', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'none', + 'sec-fetch-storage-access': 'active', + 'x-api-key': this.apiKey, + 'x-api-time': String(this.keyTime), // время ВЫДАЧИ ключа 'x-api-ts': String(sign.ts), 'x-api-nonce': sign.nonce, 'x-api-ver': sign.version, @@ -67,15 +112,35 @@ export class RabbyApiClient { private get = async (path: string, params: Record): Promise => { const headers = this.buildHeaders(params, 'GET', path); - const resp = await this.http.get(path, { params, headers }); + + let resp; + try { + resp = await this.http.get(path, { params, headers }); + } catch (error: any) { + // Ротация ключа читается ДО выброса ошибки: сервер может выдать новый + // ключ вместе с 429/403 — раньше (raise_for_status до чтения заголовка + // в эталоне, axios-throw здесь) он терялся. + const newKey = error?.response?.headers?.['x-set-api-key']; + if (newKey) { + this.keyTime = rotateKey(newKey); + this.apiKey = newKey; + } + throw error; + } const newKey = resp.headers['x-set-api-key']; - if (newKey) { + if (newKey && newKey !== this.apiKey) { + this.keyTime = rotateKey(newKey); this.apiKey = newKey; } const data = resp.data; - if (data && typeof data === 'object' && 'data' in data) { + if ( + data && + typeof data === 'object' && + 'data' in data && + Object.keys(data).every(k => k === 'data' || k === 'error_code') + ) { return data.data; } return data; @@ -99,12 +164,23 @@ export class RabbyApiClient { }; }; - /** Токены одной сети. `is_all=false` = только проверенные (core). */ + /** + * Токены кошелька по ВСЕМ сетям одним запросом (серверный кэш). + * Заменяет десятки запросов token_list (по одному на сеть) — расширение + * Rabby само использует этот эндпоинт для быстрой загрузки. Ответ — тот же + * формат токенов, фильтрация на стороне чекера. + */ + public getCacheTokenList = async (address: string): Promise => { + const result = await this.get('/v1/user/cache_token_list', { id: address.toLowerCase() }); + return Array.isArray(result) ? result : []; + }; + + /** Токены одной сети (фолбэк при сбое cache_token_list). `is_all=false` = только core. */ public getTokenList = async (address: string, chainId: string): Promise => { const result = await this.get('/v1/user/token_list', { id: address.toLowerCase(), chain_id: chainId, - is_all: 'false' + is_all: String(!RABBY.isCore) }); return Array.isArray(result) ? result : []; }; @@ -138,17 +214,19 @@ export class RabbyService extends BaseBalanceService { // 1. Авторитетный итог + сети (один запрос, is_core). const { total_usd_value, chain_list } = await client.getTotalBalance(walletAddress); - // 2. Детализация параллельно: токены по ненулевым сетям + DeFi-протоколы. const nonEmptyChains = chain_list .filter(c => (c?.usd_value || 0) > 0 && c?.id) .map(c => c.id as string); - const [tokenLists, apps] = await Promise.all([ - Promise.all(nonEmptyChains.map(chainId => client.getTokenList(walletAddress, chainId))), + // 2. Детализация параллельно: токены + DeFi-протоколы. + // Токены: все сети одним запросом (cache_token_list); при сбое — + // фолбэк на по-сетевой token_list. Пустой chain_list (пустой кошелёк) + // → запросы токенов не выполняются вовсе (так же поступает расширение). + const [tokens, apps] = await Promise.all([ + this.fetchTokens(client, walletAddress, nonEmptyChains), client.getComplexAppList(walletAddress) ]); - const tokens = tokenLists.flat(); const portfolio = this.mapAppsToPortfolio(apps); this.logger.addProcessingStep( @@ -167,6 +245,35 @@ export class RabbyService extends BaseBalanceService { return walletData; }; + private fetchTokens = async ( + client: RabbyApiClient, + address: string, + nonEmptyChains: string[] + ): Promise => { + if (nonEmptyChains.length === 0) return []; + + let tokens: any[]; + try { + tokens = await client.getCacheTokenList(address); + } catch { + tokens = []; + for (const chainId of nonEmptyChains) { + tokens = tokens.concat(await client.getTokenList(address, chainId)); + } + } + + // cache_token_list отдаёт всё подряд — скам/несоверифицированные/ + // не-core фильтруем сами (token_list с is_all=false уже отфильтрован + // сервером, повторный фильтр безвреден). + return tokens.filter( + t => + t && + t.is_verified !== false && + !t.is_scam && + (!RABBY.isCore || t.is_core !== false) + ); + }; + /** * Приводим Rabby-приложения к форме DeBank-протокола, ожидаемой билдером. * У Rabby нет сети на уровне протокола — берём её из первого токена позиции, diff --git a/backend/src/types/index.ts b/backend/src/types/index.ts index c9cd209..b1a908c 100644 --- a/backend/src/types/index.ts +++ b/backend/src/types/index.ts @@ -46,13 +46,6 @@ export type ProxyConfig = { password?: string; }; -export type DeBankApiResponse = { - user?: any; - token_balance_list?: any[]; - portfolio_list?: any[] | undefined; - total_net_curve?: any; -}; - export type AggregatedData = { totalValue: number; totalChange24h: number; diff --git a/backend/tests/rabbyService.test.ts b/backend/tests/rabbyService.test.ts index 3d074a0..6b75d72 100644 --- a/backend/tests/rabbyService.test.ts +++ b/backend/tests/rabbyService.test.ts @@ -1,10 +1,10 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import nock from 'nock'; -import { RabbyService } from '../src/services/rabbyService'; -import { CorroborationConfig } from '../src/config'; +import { RabbyService, resetKeyState } from '../src/services/rabbyService'; +import { CorroborationConfig, RABBY } from '../src/config'; const ADDR = '0x9f5dc2f69006fffae20247a95f1dfa0cb057bce9'; -const RABBY = 'https://api.rabby.io'; +const RABBY_HOST = 'https://api.rabby.io'; const cfg: CorroborationConfig = { enabled: true, @@ -14,30 +14,52 @@ const cfg: CorroborationConfig = { absTol: 1.0 }; +// ProxyService читает боевой proxy.txt из корня проекта; если там прокси с +// авторизацией, nock не сможет перехватить туннель. Подменяем на «без прокси». +const makeService = (): RabbyService => { + const service = new RabbyService(cfg); + vi.spyOn((service as any).proxyService, 'getNextProxy').mockReturnValue(null); + return service; +}; + // Мокаем ТОЛЬКО внешнюю HTTP-границу (Rabby API), а не наш код. const mockRabby = (opts: { totalUsd: number; chains: { id: string; usd_value: number }[]; - tokensByChain?: Record; + tokens?: any[]; + tokensByChain?: Record; // для фолбэка на по-сетевой token_list + cacheTokensFails?: boolean; apps?: any[]; }) => { - nock(RABBY) + nock(RABBY_HOST) .get('/v1/user/total_balance') .query(true) .reply(200, { data: { total_usd_value: opts.totalUsd, chain_list: opts.chains } }); - nock(RABBY) - .get('/v1/user/token_list') - .query(true) - .times(opts.chains.filter(c => c.usd_value > 0).length) - .reply(uri => { - const chainId = new URL(RABBY + uri).searchParams.get('chain_id') || ''; - return [200, { data: opts.tokensByChain?.[chainId] ?? [] }]; - }); + if (opts.cacheTokensFails) { + nock(RABBY_HOST) + .get('/v1/user/cache_token_list') + .query(true) + .reply(500, {}); + const chainIds = opts.chains.filter(c => c.usd_value > 0).map(c => c.id); + nock(RABBY_HOST) + .get('/v1/user/token_list') + .query(true) + .times(chainIds.length) + .reply(uri => { + const chainId = new URL(RABBY_HOST + uri).searchParams.get('chain_id') || ''; + return [200, { data: opts.tokensByChain?.[chainId] ?? [] }]; + }); + } else if (opts.tokens !== undefined) { + nock(RABBY_HOST) + .get('/v1/user/cache_token_list') + .query(true) + .reply(200, { data: opts.tokens }); + } - nock(RABBY) + nock(RABBY_HOST) .get('/v1/user/complex_app_list') .query(true) .reply(200, { data: { apps: opts.apps ?? [], error_apps: [] } }); @@ -46,6 +68,7 @@ const mockRabby = (opts: { describe('RabbyService.getWalletData', () => { beforeEach(() => { nock.disableNetConnect(); + resetKeyState(); }); afterEach(() => { nock.cleanAll(); @@ -56,9 +79,9 @@ describe('RabbyService.getWalletData', () => { mockRabby({ totalUsd: 380.0, chains: [{ id: 'soneium', usd_value: 371.32 }], - tokensByChain: { - soneium: [{ symbol: 'ETH', name: 'ETH', amount: 1, price: 371.32, chain: 'soneium', is_verified: true }] - }, + tokens: [ + { symbol: 'ETH', name: 'ETH', amount: 1, price: 371.32, chain: 'soneium', is_verified: true } + ], apps: [ { id: 'hyperliquid', @@ -70,7 +93,7 @@ describe('RabbyService.getWalletData', () => { ] }); - const service = new RabbyService(cfg); + const service = makeService(); const wd = await service.getWalletData(ADDR); expect(wd).not.toBeNull(); @@ -79,42 +102,97 @@ describe('RabbyService.getWalletData', () => { expect(wd!.protocols).toHaveLength(1); }); - it('фантомный токен в token_list НЕ раздувает итог (устранение фантом-бага)', async () => { + it('фантомный токен в детализации НЕ раздувает итог (устранение фантом-бага)', async () => { mockRabby({ totalUsd: 15.0, // авторитетный итог кошелька chains: [{ id: 'eth', usd_value: 15.0 }], - tokensByChain: { + tokens: [ // «чужой» токен-фантом на миллион — попал в детализацию, но итог его игнорирует - eth: [ - { symbol: 'ETH', name: 'ETH', amount: 1, price: 15, chain: 'eth', is_verified: true }, - { symbol: 'PHANTOM', name: 'X', amount: 1_000_000, price: 1, chain: 'eth', is_verified: true } - ] - } + { symbol: 'ETH', name: 'ETH', amount: 1, price: 15, chain: 'eth', is_verified: true }, + { symbol: 'PHANTOM', name: 'X', amount: 1_000_000, price: 1, chain: 'eth', is_verified: true } + ] }); - const service = new RabbyService(cfg); + const service = makeService(); const wd = await service.getWalletData(ADDR); expect(wd!.totalValue).toBe(15.0); // а НЕ 1_000_015 }); - it('запрашивает токены только по ненулевым сетям', async () => { + it('токены по всем сетям — ОДНИМ запросом cache_token_list', async () => { mockRabby({ totalUsd: 5, chains: [ - { id: 'eth', usd_value: 5 }, - { id: 'bsc', usd_value: 0 } // нулевую сеть не трогаем + { id: 'eth', usd_value: 3 }, + { id: 'bsc', usd_value: 2 }, + { id: 'arb', usd_value: 0 } // нулевую сеть не трогаем ], + tokens: [ + { symbol: 'ETH', name: 'ETH', amount: 1, price: 3, chain: 'eth', is_verified: true }, + { symbol: 'BNB', name: 'BNB', amount: 2, price: 1, chain: 'bsc', is_verified: true } + ] + }); + + const service = makeService(); + const wd = await service.getWalletData(ADDR); + + expect(wd!.totalValue).toBe(5); + expect(wd!.tokens).toHaveLength(2); + expect(nock.isDone()).toBe(true); // ровно 1 cache_token_list был замокан и вызван + }); + + it('при сбое cache_token_list фолбэчит на по-сетевой token_list', async () => { + mockRabby({ + totalUsd: 7, + chains: [{ id: 'eth', usd_value: 4 }, { id: 'bsc', usd_value: 3 }], + cacheTokensFails: true, tokensByChain: { - eth: [{ symbol: 'ETH', name: 'ETH', amount: 1, price: 5, chain: 'eth', is_verified: true }] + eth: [{ symbol: 'ETH', name: 'ETH', amount: 1, price: 4, chain: 'eth', is_verified: true }], + bsc: [{ symbol: 'BNB', name: 'BNB', amount: 3, price: 1, chain: 'bsc', is_verified: true }] } }); - const service = new RabbyService(cfg); + const service = makeService(); const wd = await service.getWalletData(ADDR); - expect(wd!.totalValue).toBe(5); - expect(nock.isDone()).toBe(true); // ровно 1 token_list-запрос был замокан и вызван + expect(wd!.totalValue).toBe(7); + expect(wd!.tokens).toHaveLength(2); + expect(nock.isDone()).toBe(true); + }); + + it('пустой кошелёк (chain_list пуст) не запрашивает токены вовсе', async () => { + mockRabby({ + totalUsd: 0, + chains: [] + // tokens не задан → cache_token_list даже не замокан: нежелательный + // запрос упадёт из-за disableNetConnect + }); + + const service = makeService(); + const wd = await service.getWalletData(ADDR); + + expect(wd!.totalValue).toBe(0); + expect(wd!.tokens).toHaveLength(0); + expect(nock.isDone()).toBe(true); // cache_token_list не замокан → не вызывался + }); + + it('отфильтровывает скам/неверифицированные/не-core токены из cache_token_list', async () => { + mockRabby({ + totalUsd: 15.0, + chains: [{ id: 'eth', usd_value: 15.0 }], + tokens: [ + { symbol: 'ETH', name: 'ETH', amount: 1, price: 15, chain: 'eth', is_verified: true, is_core: true }, + { symbol: 'SCAM', name: 'S', amount: 5, price: 100, chain: 'eth', is_scam: true }, + { symbol: 'UNV', name: 'U', amount: 5, price: 100, chain: 'eth', is_verified: false }, + { symbol: 'NONCORE', name: 'N', amount: 5, price: 100, chain: 'eth', is_core: false } + ] + }); + + const service = makeService(); + const wd = await service.getWalletData(ADDR); + + expect(wd!.tokens).toHaveLength(1); + expect(wd!.tokens[0].symbol).toBe('ETH'); }); it('переносит сеть DeFi-позиции из токена (у Rabby нет chain на уровне протокола)', async () => { @@ -135,9 +213,80 @@ describe('RabbyService.getWalletData', () => { ] }); - const service = new RabbyService(cfg); + const service = makeService(); const wd = await service.getWalletData(ADDR); expect(wd!.protocols[0].chain).toBe('arb'); }); }); + +describe('Заголовки и ротация ключа RabbyApiClient', () => { + beforeEach(() => { + nock.disableNetConnect(); + resetKeyState(); + }); + afterEach(() => { + nock.cleanAll(); + nock.enableNetConnect(); + }); + + it('шлёт заголовки как клиент Rabby: x-api-time = время выдачи ключа, x-version, sec-fetch-*', async () => { + nock(RABBY_HOST, { + reqheaders: { + 'x-api-key': RABBY.apiKeyInit, + 'x-api-time': String(RABBY.apiKeyInitTime), // время ВЫДАЧИ, не now + 'x-api-ver': 'v2', + 'x-client': 'Rabby', + 'x-version': RABBY.clientVersion, + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'none', + 'sec-fetch-storage-access': 'active', + 'accept-language': /ru/, + dnt: '1', + priority: 'u=1, i' + } + }) + .get('/v1/user/total_balance') + .query(true) + .reply(200, { data: { total_usd_value: 1, chain_list: [] } }); + + const { RabbyApiClient } = await import('../src/services/rabbyService'); + const client = new RabbyApiClient(null, 1000); + const total = await client.getTotalBalance(ADDR); + + expect(total.total_usd_value).toBe(1); + expect(nock.isDone()).toBe(true); + }); + + it('ротированный x-set-api-key переживает ошибочный ответ и новый клиент', async () => { + const rotatedKey = 'rotated-key-42'; + + // Первый запрос: 429, но вместе с ним выдан новый ключ — читаем ДО ошибки. + nock(RABBY_HOST) + .get('/v1/user/total_balance') + .query(true) + .reply(429, '', { 'x-set-api-key': rotatedKey }); + + const { RabbyApiClient } = await import('../src/services/rabbyService'); + const first = new RabbyApiClient(null, 1000); + await expect(first.getTotalBalance(ADDR)).rejects.toThrow(); + + // Новый клиент (новая попытка снапшота) должен продолжить с ротированным ключом. + nock(RABBY_HOST, { + reqheaders: { + 'x-api-key': rotatedKey, + // время ротации (now), а не init-время выдачи старого ключа + 'x-api-time': /^(?!1762656362$)\d{10}$/ + } + }) + .get('/v1/user/total_balance') + .query(true) + .reply(200, { data: { total_usd_value: 2, chain_list: [] } }); + + const second = new RabbyApiClient(null, 1000); + const total = await second.getTotalBalance(ADDR); + expect(total.total_usd_value).toBe(2); + expect(nock.isDone()).toBe(true); + }); +});