diff --git a/services/token-aggregation/deploy-to-vmid.sh b/services/token-aggregation/deploy-to-vmid.sh index 99b2904..d42a21c 100755 --- a/services/token-aggregation/deploy-to-vmid.sh +++ b/services/token-aggregation/deploy-to-vmid.sh @@ -26,7 +26,7 @@ log_ok "Built" # Package log_info "Creating package..." -PACKAGE_ITEMS=(dist src package.json tsconfig.json) +PACKAGE_ITEMS=(dist src package.json package-lock.json tsconfig.json scripts) for optional in .env.example .env; do [ -e "$SCRIPT_DIR/$optional" ] && PACKAGE_ITEMS+=("$optional") done @@ -41,10 +41,10 @@ ssh "$PROXMOX_USER@$PROXMOX_HOST" " if ! command -v node &>/dev/null; then curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs postgresql-client fi - command -v pnpm &>/dev/null || npm install -g pnpm@10 mkdir -p /opt/token-aggregation && cd /opt/token-aggregation tar xzf /tmp/token-agg.tar.gz - pnpm install --prod + npm ci --omit=dev + npm_config_build_from_source=true npm rebuild bcrypt >/dev/null 2>&1 || true if [ ! -f .env ] && [ -f .env.example ]; then cp .env.example .env fi @@ -62,7 +62,8 @@ Type=simple User=root WorkingDirectory=/opt/token-aggregation Environment=\"NODE_ENV=production\" -ExecStart=/usr/bin/pnpm start +Environment=\"PORT=$SERVICE_PORT\" +ExecStart=/usr/bin/node /opt/token-aggregation/dist/index.js Restart=on-failure RestartSec=5s diff --git a/services/token-aggregation/docs/REST_API_REFERENCE.md b/services/token-aggregation/docs/REST_API_REFERENCE.md index b2f32bc..af904c2 100644 --- a/services/token-aggregation/docs/REST_API_REFERENCE.md +++ b/services/token-aggregation/docs/REST_API_REFERENCE.md @@ -177,6 +177,39 @@ OHLCV (candlestick) data for charts. **Response:** `{ chainId, tokenAddress, interval, data: OHLCV[] }` +### GET /api/v1/tokens/:address/price-at + +Point-in-time USD valuation for a token at or immediately before a requested timestamp. Intended for explorer transaction detail pages where the transfer-time value should stay anchored to the transaction, while wallet/address/token overview pages can continue showing current market value. + +**Query:** + +| Param | Type | Required | Description | +|-------|------|----------|-------------| +| chainId | number | yes | 138 or 651940 | +| timestamp | ISO date | yes | Transfer time to price against | + +**Response:** `{ chainId, tokenAddress, requestedTimestamp, effectiveTimestamp?, priceUsd?, source }` + +- `source` is one of `swap_event`, `ohlcv_5m`, `ohlcv_15m`, `ohlcv_1h`, `ohlcv_4h`, `ohlcv_24h`, `current_market_fallback`, `canonical_fallback`, or `unavailable` +- `effectiveTimestamp` is the candle/snapshot time actually used for valuation + +### GET /api/v1/tokens/:address/pricing-context + +Snap-ready combined pricing response for one token. Returns the current market valuation and, when `timestamp` is supplied, a transfer-time historical valuation in the same payload. + +**Query:** + +| Param | Type | Required | Description | +|-------|------|----------|-------------| +| chainId | number | yes | 138 or 651940 | +| timestamp | ISO date | no | Transfer time to price against | + +**Response:** `{ chainId, tokenAddress, current, historical? }` + +- `current` includes `{ chainId, tokenAddress, priceUsd?, asOf?, sourceLayer, stale, marketLastUpdated? }` +- `historical` includes `{ chainId, tokenAddress, requestedTimestamp, effectiveTimestamp?, priceUsd?, source, locked }` +- `locked` is `true` only when the historical price came from `swap_event` or an `ohlcv_*` source; fallback values are returned with `locked: false` + ### GET /api/v1/tokens/:address/signals Trending/signals (e.g. CoinGecko trending rank) for a token. diff --git a/services/token-aggregation/package-lock.json b/services/token-aggregation/package-lock.json index 9d5ed33..1bed3a3 100644 --- a/services/token-aggregation/package-lock.json +++ b/services/token-aggregation/package-lock.json @@ -8,32 +8,32 @@ "name": "token-aggregation-service", "version": "1.0.0", "dependencies": { - "axios": "^1.13.5", - "bcrypt": "^5.1.1", + "axios": "^1.15.2", + "bcrypt": "^6.0.0", "compression": "^1.8.1", "cors": "^2.8.6", "dotenv": "^16.6.1", "ethers": "^6.16.0", - "express": "^4.22.1", - "express-rate-limit": "^7.5.1", + "express": "^5.1.0", + "express-rate-limit": "^8.4.1", "jsonwebtoken": "^9.0.3", - "node-cron": "^3.0.3", + "node-cron": "^4.2.1", "pg": "^8.18.0", "winston": "^3.19.0" }, "devDependencies": { - "@types/bcrypt": "^5.0.2", + "@types/bcrypt": "^6.0.0", "@types/compression": "^1.8.1", "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", - "@types/express": "^4.17.25", + "@types/express": "^5.0.6", "@types/jest": "^29.5.14", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^20.19.33", "@types/node-cron": "^3.0.11", "@types/pg": "^8.16.0", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^8.59.0", + "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.1", "jest": "^29.7.0", "ts-jest": "^29.4.6", @@ -78,7 +78,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -661,30 +660,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", @@ -711,30 +686,6 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1216,26 +1167,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "license": "BSD-3-Clause", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, "node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -1409,9 +1340,9 @@ } }, "node_modules/@types/bcrypt": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", - "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1471,23 +1402,21 @@ } }, "node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", - "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "dev": true, "license": "MIT", "dependencies": { @@ -1552,13 +1481,6 @@ "pretty-format": "^29.0.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", @@ -1570,13 +1492,6 @@ "@types/node": "*" } }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -1590,7 +1505,6 @@ "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1615,9 +1529,9 @@ } }, "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", "dev": true, "license": "MIT" }, @@ -1628,13 +1542,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", @@ -1646,25 +1553,13 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, @@ -1699,125 +1594,159 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz", + "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/type-utils": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.59.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz", + "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "dev": true, - "license": "BSD-2-Clause", - "peer": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "debug": "^4.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz", + "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.0", + "@typescript-eslint/types": "^8.59.0", + "debug": "^4.4.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz", + "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz", + "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz", + "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz", + "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==", "dev": true, "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1825,76 +1754,86 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz", + "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@typescript-eslint/project-service": "8.59.0", + "@typescript-eslint/tsconfig-utils": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "node_modules/@typescript-eslint/utils": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz", + "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz", + "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@ungap/structured-clone": { @@ -1904,29 +1843,39 @@ "dev": true, "license": "ISC" }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC" - }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1938,7 +1887,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1975,22 +1923,10 @@ "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", "license": "MIT" }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2037,6 +1973,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2072,26 +2009,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "license": "ISC" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2106,22 +2023,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -2135,9 +2036,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", - "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", @@ -2275,6 +2176,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -2288,62 +2190,47 @@ } }, "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" }, "engines": { - "node": ">= 10.0.0" + "node": ">= 18" } }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -2383,7 +2270,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2540,15 +2426,6 @@ "node": ">=10" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -2659,15 +2536,6 @@ "node": ">=12.20" } }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, "node_modules/color/node_modules/color-convert": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", @@ -2746,28 +2614,17 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC" - }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -2796,10 +2653,13 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cors": { "version": "2.8.6", @@ -2920,12 +2780,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT" - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2935,25 +2789,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2984,19 +2819,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3075,6 +2897,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/enabled": { @@ -3183,7 +3006,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3264,30 +3086,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -3469,45 +3267,41 @@ } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", @@ -3515,10 +3309,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, "engines": { "node": ">= 16" }, @@ -3529,21 +3326,22 @@ "express": ">= 4.11" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3551,36 +3349,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3615,6 +3383,24 @@ "bser": "2.1.1" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -3648,38 +3434,26 @@ } }, "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3713,9 +3487,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -3726,9 +3500,9 @@ "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -3771,48 +3545,19 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -3839,27 +3584,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3945,6 +3669,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -3974,28 +3699,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -4012,27 +3715,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4060,9 +3742,9 @@ "license": "MIT" }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4118,12 +3800,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC" - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4163,19 +3839,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -4187,15 +3850,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ignore": { @@ -4260,6 +3927,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -4272,6 +3940,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -4318,6 +3995,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4366,6 +4044,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -4478,7 +4162,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -5329,30 +5012,6 @@ "yallist": "^3.0.2" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5380,19 +5039,22 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -5404,25 +5066,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -5437,18 +5080,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -5490,13 +5121,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5515,58 +5146,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5597,41 +5176,32 @@ "license": "MIT" }, "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", - "license": "MIT" + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, "node_modules/node-cron": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", - "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz", + "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", "license": "ISC", - "dependencies": { - "uuid": "8.3.2" - }, "engines": { "node": ">=6.0.0" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" } }, "node_modules/node-int64": { @@ -5648,21 +5218,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -5686,19 +5241,6 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5890,6 +5432,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5913,19 +5456,13 @@ "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/pg": { @@ -5933,7 +5470,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -6026,9 +5562,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -6258,9 +5794,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -6303,18 +5839,18 @@ } }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/react-is": { @@ -6428,6 +5964,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -6439,6 +5976,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6511,65 +6064,66 @@ } }, "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -6619,13 +6173,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -6675,6 +6229,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, "license": "ISC" }, "node_modules/sisteransi": { @@ -6799,6 +6354,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -6813,6 +6369,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -6880,30 +6437,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6919,30 +6452,6 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -6956,6 +6465,23 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -6985,12 +6511,6 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -7001,16 +6521,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-jest": { @@ -7085,7 +6605,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -7167,25 +6686,41 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7271,24 +6806,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -7330,22 +6847,6 @@ "makeerror": "1.0.12" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7362,15 +6863,6 @@ "node": ">= 8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/winston": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", diff --git a/services/token-aggregation/package.json b/services/token-aggregation/package.json index e44c1f5..066fc2e 100644 --- a/services/token-aggregation/package.json +++ b/services/token-aggregation/package.json @@ -2,7 +2,7 @@ "name": "token-aggregation-service", "version": "1.0.0", "description": "Token aggregation service for ChainID 138 and 651940 with external API enrichment", - "packageManager": "pnpm@10.0.0", + "packageManager": "npm@10.8.2", "main": "dist/index.js", "scripts": { "build": "tsc", @@ -12,38 +12,48 @@ "test:ci": "jest --runInBand", "test:omnl": "jest --runInBand --testPathPattern=omnl", "lint": "eslint src --ext .ts", + "backfill:historical-pricing": "node dist/backfill-historical-pricing.js", "generate:route-matrix:v2": "ts-node scripts/generate-route-matrix-v2.ts", "migrate": "node -r dotenv/config dist/database/migrations.js", "example:partner-payloads": "node scripts/resolve-partner-payloads-example.mjs", "omnl:reconcile": "node scripts/omnl-reconcile-report.mjs" }, "dependencies": { - "axios": "^1.13.5", - "bcrypt": "^5.1.1", + "axios": "^1.15.2", + "bcrypt": "^6.0.0", "compression": "^1.8.1", "cors": "^2.8.6", "dotenv": "^16.6.1", "ethers": "^6.16.0", - "express": "^4.22.1", - "express-rate-limit": "^7.5.1", + "express": "^5.1.0", + "express-rate-limit": "^8.4.1", "jsonwebtoken": "^9.0.3", - "node-cron": "^3.0.3", + "node-cron": "^4.2.1", "pg": "^8.18.0", "winston": "^3.19.0" }, + "overrides": { + "ajv": "^6.14.0", + "brace-expansion": "^2.0.3", + "flatted": "^3.4.1", + "follow-redirects": "1.16.0", + "handlebars": "^4.7.9", + "minimatch": "^9.0.7", + "picomatch": "^2.3.2" + }, "devDependencies": { - "@types/bcrypt": "^5.0.2", + "@types/bcrypt": "^6.0.0", "@types/compression": "^1.8.1", "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", - "@types/express": "^4.17.25", + "@types/express": "^5.0.6", "@types/jest": "^29.5.14", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^20.19.33", "@types/node-cron": "^3.0.11", "@types/pg": "^8.16.0", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^8.59.0", + "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.1", "jest": "^29.7.0", "ts-jest": "^29.4.6", diff --git a/services/token-aggregation/scripts/backfill-historical-pricing.ts b/services/token-aggregation/scripts/backfill-historical-pricing.ts new file mode 100644 index 0000000..dbb04e2 --- /dev/null +++ b/services/token-aggregation/scripts/backfill-historical-pricing.ts @@ -0,0 +1,49 @@ +import * as dotenv from 'dotenv'; +import path from 'path'; +import { existsSync } from 'fs'; +import { HistoricalPricingBackfillService } from '../src/services/historical-pricing-backfill'; + +const rootEnvCandidates = [ + path.resolve(__dirname, '../../../.env'), + path.resolve(__dirname, '../../../../.env'), +]; + +for (const candidate of rootEnvCandidates) { + if (existsSync(candidate)) { + dotenv.config({ path: candidate }); + break; + } +} + +dotenv.config(); + +function readInt(name: string, fallback: number): number { + const raw = String(process.env[name] || '').trim(); + if (!raw) return fallback; + const parsed = Number(raw); + return Number.isFinite(parsed) ? parsed : fallback; +} + +async function main(): Promise { + const chainId = readInt('BACKFILL_CHAIN_ID', 138); + const days = readInt('BACKFILL_DAYS', 30); + const chunkSize = readInt('BACKFILL_CHUNK_SIZE', 2500); + const poolLimit = readInt('BACKFILL_POOL_LIMIT', 500); + + const service = new HistoricalPricingBackfillService(); + const summary = await service.backfillChain({ + chainId, + days, + chunkSize, + poolLimit, + }); + + // eslint-disable-next-line no-console + console.log(JSON.stringify(summary, null, 2)); +} + +main().catch((error) => { + // eslint-disable-next-line no-console + console.error(error); + process.exit(1); +}); diff --git a/services/token-aggregation/scripts/bootstrap-lightweight-schema.sql b/services/token-aggregation/scripts/bootstrap-lightweight-schema.sql index ddf0d6c..d3b9818 100644 --- a/services/token-aggregation/scripts/bootstrap-lightweight-schema.sql +++ b/services/token-aggregation/scripts/bootstrap-lightweight-schema.sql @@ -91,13 +91,21 @@ CREATE TABLE IF NOT EXISTS swap_events ( id BIGSERIAL PRIMARY KEY, chain_id INTEGER NOT NULL, pool_address TEXT NOT NULL, + transaction_hash TEXT, + block_number BIGINT, + log_index INTEGER, token0_address TEXT NOT NULL, token1_address TEXT NOT NULL, + amount0_in NUMERIC(78, 0) NOT NULL DEFAULT 0, + amount1_in NUMERIC(78, 0) NOT NULL DEFAULT 0, + amount0_out NUMERIC(78, 0) NOT NULL DEFAULT 0, + amount1_out NUMERIC(78, 0) NOT NULL DEFAULT 0, amount_usd NUMERIC(38, 18) NOT NULL DEFAULT 0, price_usd NUMERIC(38, 18), - transaction_hash TEXT, - log_index INTEGER, - block_number BIGINT, + token0_price_usd NUMERIC(38, 18), + token1_price_usd NUMERIC(38, 18), + sender TEXT, + to_address TEXT, timestamp TIMESTAMPTZ NOT NULL ); @@ -105,13 +113,9 @@ CREATE INDEX IF NOT EXISTS idx_swap_events_pool_time ON swap_events (chain_id, pool_address, timestamp DESC); CREATE INDEX IF NOT EXISTS idx_swap_events_token_time ON swap_events (chain_id, token0_address, token1_address, timestamp DESC); -CREATE UNIQUE INDEX IF NOT EXISTS idx_swap_events_unique_log - ON swap_events ( - chain_id, - pool_address, - COALESCE(transaction_hash, ''), - COALESCE(log_index, -1) - ); +DROP INDEX IF EXISTS idx_swap_events_unique_log; +CREATE UNIQUE INDEX IF NOT EXISTS idx_swap_events_chain_tx_log + ON swap_events (chain_id, transaction_hash, log_index); CREATE TABLE IF NOT EXISTS token_ohlcv ( id BIGSERIAL PRIMARY KEY, diff --git a/services/token-aggregation/src/adapters/coingecko-adapter.ts b/services/token-aggregation/src/adapters/coingecko-adapter.ts index b30914a..7bb1d88 100644 --- a/services/token-aggregation/src/adapters/coingecko-adapter.ts +++ b/services/token-aggregation/src/adapters/coingecko-adapter.ts @@ -67,6 +67,10 @@ interface CoinGeckoTrending { }>; } +interface CoinGeckoMarketChartRangeResponse { + prices?: Array<[number, number]>; +} + // Chain ID to CoinGecko platform ID mapping const CHAIN_TO_PLATFORM: Record = { 1: 'ethereum', @@ -79,6 +83,17 @@ const CHAIN_TO_PLATFORM: Record = { // Note: 138 and 651940 are likely not supported, will return null gracefully }; +const REFERENCE_SYMBOL_TO_COIN_ID: Record = { + ETH: 'ethereum', + BTC: 'bitcoin', + BNB: 'binancecoin', + POL: 'matic-network', + AVAX: 'avalanche-2', + CELO: 'celo', + CRO: 'crypto-com-chain', + XDAI: 'xdai', +}; + export class CoinGeckoAdapter implements ExternalApiAdapter { private api: AxiosInstance; private apiKey?: string; @@ -322,4 +337,73 @@ export class CoinGeckoAdapter implements ExternalApiAdapter { return []; } } + + async getHistoricalReferencePrice( + referenceSymbol: string, + timestamp: Date + ): Promise<{ priceUsd: number; effectiveTimestamp: Date } | null> { + const symbol = referenceSymbol.trim().toUpperCase(); + const coinId = REFERENCE_SYMBOL_TO_COIN_ID[symbol]; + if (!coinId) { + return null; + } + + const from = Math.floor((timestamp.getTime() - (6 * 60 * 60 * 1000)) / 1000); + const to = Math.floor((timestamp.getTime() + (6 * 60 * 60 * 1000)) / 1000); + const cacheKey = `history_${coinId}_${from}_${to}`; + const cached = this.cache.get(cacheKey); + if (cached && cached.expiresAt > new Date()) { + return cached.data as { priceUsd: number; effectiveTimestamp: Date } | null; + } + + try { + const response = await this.api.get( + `/coins/${coinId}/market_chart/range`, + { + params: { + vs_currency: 'usd', + from, + to, + }, + } + ); + + const prices = response.data.prices || []; + if (prices.length === 0) { + return null; + } + + const targetMs = timestamp.getTime(); + let bestPoint: [number, number] | null = null; + let bestDistance = Number.POSITIVE_INFINITY; + + for (const point of prices) { + if (point[0] > targetMs) { + continue; + } + const distance = Math.abs(point[0] - targetMs); + if (distance < bestDistance) { + bestDistance = distance; + bestPoint = point; + } + } + + if (!bestPoint || !Number.isFinite(bestPoint[1])) { + return null; + } + + const resolved = { + priceUsd: bestPoint[1], + effectiveTimestamp: new Date(bestPoint[0]), + }; + this.cache.set(cacheKey, { + data: resolved, + expiresAt: new Date(Date.now() + 60 * 60 * 1000), + }); + return resolved; + } catch (error) { + logger.error(`Error fetching CoinGecko historical reference price for ${referenceSymbol}:`, error); + return null; + } + } } diff --git a/services/token-aggregation/src/api/routes/admin.ts b/services/token-aggregation/src/api/routes/admin.ts index c8ed227..1e6d0ec 100644 --- a/services/token-aggregation/src/api/routes/admin.ts +++ b/services/token-aggregation/src/api/routes/admin.ts @@ -8,6 +8,10 @@ import { appendCentralAudit } from '../central-audit'; const router: Router = Router(); const adminRepo = new AdminRepository(); +function firstString(value: string | string[] | undefined): string | undefined { + return Array.isArray(value) ? value[0] : value; +} + // Authentication routes (public) router.post('/auth/login', async (req: Request, res: Response) => { try { @@ -110,7 +114,7 @@ router.post('/api-keys', requireRole('admin', 'super_admin'), async (req: AuthRe router.put('/api-keys/:id', requireRole('admin', 'super_admin'), async (req: AuthRequest, res: Response) => { try { - const id = parseInt(req.params.id, 10); + const id = parseInt(firstString(req.params.id) || '', 10); const updates: { isActive?: boolean; rateLimitPerMinute?: number; expiresAt?: Date } = {}; if (req.body.isActive !== undefined) updates.isActive = req.body.isActive; @@ -142,7 +146,7 @@ router.put('/api-keys/:id', requireRole('admin', 'super_admin'), async (req: Aut router.delete('/api-keys/:id', requireRole('admin', 'super_admin'), async (req: AuthRequest, res: Response) => { try { - const id = parseInt(req.params.id, 10); + const id = parseInt(firstString(req.params.id) || '', 10); const oldKey = await adminRepo.getApiKey(id); await adminRepo.deleteApiKey(id); @@ -237,7 +241,7 @@ router.post('/endpoints', requireRole('admin', 'super_admin'), async (req: AuthR router.put('/endpoints/:id', requireRole('admin', 'super_admin'), async (req: AuthRequest, res: Response) => { try { - const id = parseInt(req.params.id, 10); + const id = parseInt(firstString(req.params.id) || '', 10); const updates: { endpointUrl?: string; isActive?: boolean; isPrimary?: boolean } = {}; if (req.body.endpointUrl !== undefined) updates.endpointUrl = req.body.endpointUrl; diff --git a/services/token-aggregation/src/api/routes/omnl-ipsas.ts b/services/token-aggregation/src/api/routes/omnl-ipsas.ts index 1aba7d4..a8f6bc8 100644 --- a/services/token-aggregation/src/api/routes/omnl-ipsas.ts +++ b/services/token-aggregation/src/api/routes/omnl-ipsas.ts @@ -166,7 +166,7 @@ router.get('/omnl/ipsas/fineract-compare', omnlSensitiveRouteGuard, async (_req: */ router.get('/omnl/ipsas/layer/:layer', (req: Request, res: Response) => { try { - const layer = req.params.layer; + const layer = Array.isArray(req.params.layer) ? req.params.layer[0] : req.params.layer; const registry = loadIpsasRegistry(); const hint = registry.monetaryLayerHints[layer]; if (!hint) { diff --git a/services/token-aggregation/src/api/routes/tokens.test.ts b/services/token-aggregation/src/api/routes/tokens.test.ts index 1d25514..973efac 100644 --- a/services/token-aggregation/src/api/routes/tokens.test.ts +++ b/services/token-aggregation/src/api/routes/tokens.test.ts @@ -12,6 +12,8 @@ const mockGetLiveDodoPools = jest.fn(); const mockResolveTokenDisplay = jest.fn(); const mockResolvePoolTokenDisplays = jest.fn(); const mockGetTokenByContract = jest.fn(); +const mockGetOHLCV = jest.fn(); +const mockGetHistoricalReferencePrice = jest.fn(); jest.mock('../../database/repositories/token-repo', () => ({ TokenRepository: jest.fn().mockImplementation(() => ({ @@ -36,7 +38,7 @@ jest.mock('../../database/repositories/pool-repo', () => ({ jest.mock('../../indexer/ohlcv-generator', () => ({ OHLCVGenerator: jest.fn().mockImplementation(() => ({ - getOHLCV: jest.fn().mockResolvedValue([]), + getOHLCV: mockGetOHLCV, })), })); @@ -46,6 +48,7 @@ jest.mock('../../adapters/coingecko-adapter', () => ({ CoinGeckoAdapter: jest.fn().mockImplementation(() => ({ getTokenByContract: mockGetTokenByContract, getMarketData: mockGetMarketDataAdapter, + getHistoricalReferencePrice: mockGetHistoricalReferencePrice, getTrending: jest.fn().mockResolvedValue([]), })), })); @@ -110,6 +113,7 @@ describe('Tokens API', () => { mockGetMarketData.mockResolvedValue(null); mockGetPoolsByToken.mockResolvedValue([]); mockGetPool.mockResolvedValue(null); + mockGetOHLCV.mockResolvedValue([]); mockGetLiveDodoPools.mockResolvedValue([]); mockResolveTokenDisplay.mockResolvedValue({ address: '', @@ -123,6 +127,7 @@ describe('Tokens API', () => { token1: { address: '', symbol: 'UNKNOWN', name: 'Unknown Token', source: 'fallback' }, }); mockGetTokenByContract.mockResolvedValue(null); + mockGetHistoricalReferencePrice.mockResolvedValue(null); }); afterAll(async () => { @@ -216,4 +221,170 @@ describe('Tokens API', () => { }); expect(body.token.canonicalLiquidity).toBeUndefined(); }); + + it('returns historical price snapshots for a token at a requested timestamp', async () => { + const weth = getCanonicalTokenBySymbol(138, 'WETH'); + expect(weth?.addresses[138]).toBeTruthy(); + const wethAddress = String(weth?.addresses[138]).toLowerCase(); + + mockGetOHLCV + .mockResolvedValueOnce([ + { + timestamp: new Date('2026-04-26T01:30:00.000Z'), + open: 2488, + high: 2491, + low: 2487, + close: 2490, + volume: 10, + volumeUsd: 24900, + }, + ]); + + const res = await fetch( + `${baseUrl}/api/v1/tokens/${wethAddress}/price-at?chainId=138×tamp=${encodeURIComponent('2026-04-26T01:33:02.000Z')}` + ); + expect(res.status).toBe(200); + + const body = (await res.json()) as Record; + expect(body).toMatchObject({ + chainId: 138, + tokenAddress: wethAddress, + requestedTimestamp: '2026-04-26T01:33:02.000Z', + effectiveTimestamp: '2026-04-26T01:30:00.000Z', + priceUsd: 2490, + source: 'ohlcv_5m', + }); + }); + + it('returns pricing context with current and locked historical snapshots', async () => { + const weth = getCanonicalTokenBySymbol(138, 'WETH'); + expect(weth?.addresses[138]).toBeTruthy(); + const wethAddress = String(weth?.addresses[138]).toLowerCase(); + + mockGetOHLCV.mockResolvedValueOnce([ + { + timestamp: new Date('2026-04-26T01:30:00.000Z'), + open: 2488, + high: 2491, + low: 2487, + close: 2490, + volume: 10, + volumeUsd: 24900, + }, + ]); + + mockGetMarketData.mockResolvedValue({ + chainId: 138, + tokenAddress: wethAddress, + priceUsd: 2490, + volume24h: 1000, + volume7d: 2000, + volume30d: 3000, + liquidityUsd: 4000, + holdersCount: 5, + transfers24h: 6, + lastUpdated: new Date('2026-04-26T03:31:01.988Z'), + }); + + const res = await fetch( + `${baseUrl}/api/v1/tokens/${wethAddress}/pricing-context?chainId=138×tamp=${encodeURIComponent('2026-04-26T01:33:02.000Z')}` + ); + expect(res.status).toBe(200); + + const body = (await res.json()) as Record; + expect(body).toMatchObject({ + chainId: 138, + tokenAddress: wethAddress, + current: { + chainId: 138, + tokenAddress: wethAddress, + priceUsd: 2490, + stale: false, + }, + historical: { + chainId: 138, + tokenAddress: wethAddress, + requestedTimestamp: '2026-04-26T01:33:02.000Z', + effectiveTimestamp: '2026-04-26T01:30:00.000Z', + priceUsd: 2490, + source: 'ohlcv_5m', + locked: true, + }, + }); + expect(body.current.asOf).toEqual(expect.any(String)); + expect(body.current.sourceLayer).toEqual(expect.any(String)); + }); + + it('falls back to canonical CoinGecko history for native reference assets when local OHLCV is unavailable', async () => { + const weth = getCanonicalTokenBySymbol(138, 'WETH'); + expect(weth?.addresses[138]).toBeTruthy(); + const wethAddress = String(weth?.addresses[138]).toLowerCase(); + + mockGetHistoricalReferencePrice.mockResolvedValue({ + priceUsd: 2484.12, + effectiveTimestamp: new Date('2026-04-26T01:31:00.000Z'), + }); + + const res = await fetch( + `${baseUrl}/api/v1/tokens/${wethAddress}/pricing-context?chainId=138×tamp=${encodeURIComponent('2026-04-26T01:33:02.000Z')}` + ); + expect(res.status).toBe(200); + + const body = (await res.json()) as Record; + expect(body.historical).toMatchObject({ + chainId: 138, + tokenAddress: wethAddress, + requestedTimestamp: '2026-04-26T01:33:02.000Z', + effectiveTimestamp: '2026-04-26T01:31:00.000Z', + priceUsd: 2484.12, + source: 'coingecko_history', + locked: true, + }); + }); + + it('does not lock stale OHLCV candles that are too far before the requested timestamp', async () => { + const usdt = getCanonicalTokenBySymbol(138, 'USDT'); + expect(usdt?.addresses[138]).toBeTruthy(); + const usdtAddress = String(usdt?.addresses[138]).toLowerCase(); + + mockGetOHLCV.mockResolvedValue([ + { + timestamp: new Date('2026-04-11T00:00:00.000Z'), + open: 1, + high: 1, + low: 1, + close: 1, + volume: 15, + volumeUsd: 15, + }, + ]); + + mockGetHistoricalReferencePrice.mockResolvedValue(null); + mockGetMarketData.mockResolvedValue({ + chainId: 138, + tokenAddress: usdtAddress, + priceUsd: 1, + volume24h: 0, + volume7d: 0, + volume30d: 0, + liquidityUsd: 0, + holdersCount: 0, + transfers24h: 0, + lastUpdated: new Date('2026-04-26T03:31:01.988Z'), + }); + + const res = await fetch( + `${baseUrl}/api/v1/tokens/${usdtAddress}/pricing-context?chainId=138×tamp=${encodeURIComponent('2026-04-26T01:33:02.000Z')}` + ); + expect(res.status).toBe(200); + + const body = (await res.json()) as Record; + expect(body.historical).toMatchObject({ + chainId: 138, + tokenAddress: usdtAddress, + requestedTimestamp: '2026-04-26T01:33:02.000Z', + source: 'current_market_fallback', + locked: false, + }); + }); }); diff --git a/services/token-aggregation/src/api/routes/tokens.ts b/services/token-aggregation/src/api/routes/tokens.ts index 6fac348..11454b4 100644 --- a/services/token-aggregation/src/api/routes/tokens.ts +++ b/services/token-aggregation/src/api/routes/tokens.ts @@ -15,6 +15,7 @@ import { getCanonicalTokensByChain, resolveCanonicalQuoteAddress, } from '../../config/canonical-tokens'; +import { resolveCanonicalPriceUsd } from '../../services/canonical-price-oracle'; import { getLiveDodoPools } from '../../services/live-dodo-fallback'; import { buildExplorerLinks, @@ -27,10 +28,64 @@ const tokenRepo = new TokenRepository(); const marketDataRepo = new MarketDataRepository(); const poolRepo = new PoolRepository(); const ohlcvGenerator = new OHLCVGenerator(); + +type HistoricalPriceSource = + | 'swap_event' + | 'ohlcv_5m' + | 'ohlcv_15m' + | 'ohlcv_1h' + | 'ohlcv_4h' + | 'ohlcv_24h' + | 'coingecko_history' + | 'current_market_fallback' + | 'canonical_fallback' + | 'unavailable'; const coingeckoAdapter = new CoinGeckoAdapter(); const cmcAdapter = new CoinMarketCapAdapter(); const dexscreenerAdapter = new DexScreenerAdapter(); +function firstString(value: string | string[] | undefined): string | undefined { + return Array.isArray(value) ? value[0] : value; +} + +type HistoricalPriceSnapshot = { + chainId: number; + tokenAddress: string; + requestedTimestamp: string; + effectiveTimestamp?: string; + priceUsd?: number; + source: HistoricalPriceSource; +}; + +type CurrentPriceSnapshot = { + chainId: number; + tokenAddress: string; + priceUsd?: number; + asOf?: string; + sourceLayer: string; + stale: boolean; + marketLastUpdated?: string; +}; + +const HISTORICAL_INTERVAL_MAX_AGE_MS: Record<'5m' | '15m' | '1h' | '4h' | '24h', number> = { + '5m': 15 * 60 * 1000, + '15m': 45 * 60 * 1000, + '1h': 3 * 60 * 60 * 1000, + '4h': 12 * 60 * 60 * 1000, + '24h': 36 * 60 * 60 * 1000, +}; + +const COINGECKO_HISTORY_MAX_SKEW_MS = 6 * 60 * 60 * 1000; + +function isAcceptableHistoricalTimestamp( + candidateTimestamp: Date, + requestedTimestamp: Date, + maxAgeMs: number +): boolean { + const deltaMs = requestedTimestamp.getTime() - candidateTimestamp.getTime(); + return deltaMs >= 0 && deltaMs <= maxAgeMs; +} + function buildMarketPricingExplorer( chainId: number, displayAddress: string, @@ -126,6 +181,189 @@ async function getTokenWithFallback(chainId: number, address: string): Promise { + const normalizedAddress = address.toLowerCase(); + const resolution = resolveCanonicalQuoteAddress(chainId, normalizedAddress); + const requestedTimestamp = timestamp.toISOString(); + + try { + const direct5m = await ohlcvGenerator.getOHLCV( + chainId, + resolution.lookupAddress, + '5m', + new Date(timestamp.getTime() - 10 * 60 * 1000), + timestamp + ); + const directCandidate = direct5m[direct5m.length - 1]; + if ( + directCandidate?.close != null && + isAcceptableHistoricalTimestamp( + directCandidate.timestamp, + timestamp, + HISTORICAL_INTERVAL_MAX_AGE_MS['5m'] + ) + ) { + return { + chainId, + tokenAddress: normalizedAddress, + requestedTimestamp, + effectiveTimestamp: directCandidate.timestamp.toISOString(), + priceUsd: directCandidate.close, + source: 'ohlcv_5m', + }; + } + + const intervalWindows: Array<{ interval: '15m' | '1h' | '4h' | '24h'; windowMs: number }> = [ + { interval: '15m', windowMs: 2 * 60 * 60 * 1000 }, + { interval: '1h', windowMs: 24 * 60 * 60 * 1000 }, + { interval: '4h', windowMs: 7 * 24 * 60 * 60 * 1000 }, + { interval: '24h', windowMs: 60 * 24 * 60 * 60 * 1000 }, + ]; + + for (const { interval, windowMs } of intervalWindows) { + const candles = await ohlcvGenerator.getOHLCV( + chainId, + resolution.lookupAddress, + interval, + new Date(timestamp.getTime() - windowMs), + timestamp + ); + const candidate = candles[candles.length - 1]; + if ( + candidate?.close != null && + isAcceptableHistoricalTimestamp( + candidate.timestamp, + timestamp, + HISTORICAL_INTERVAL_MAX_AGE_MS[interval] + ) + ) { + return { + chainId, + tokenAddress: normalizedAddress, + requestedTimestamp, + effectiveTimestamp: candidate.timestamp.toISOString(), + priceUsd: candidate.close, + source: `ohlcv_${interval}`, + }; + } + } + } catch (error) { + logger.warn('Historical OHLCV lookup failed; falling back to current layers', { + chainId, + address: resolution.lookupAddress, + requestedTimestamp, + error, + }); + } + + const canonicalResolution = resolveCanonicalPriceUsd(chainId, resolution.lookupAddress); + if (canonicalResolution.referenceSymbol) { + const coingeckoHistorical = await coingeckoAdapter.getHistoricalReferencePrice( + canonicalResolution.referenceSymbol, + timestamp + ); + if ( + coingeckoHistorical?.priceUsd != null && + isAcceptableHistoricalTimestamp( + coingeckoHistorical.effectiveTimestamp, + timestamp, + COINGECKO_HISTORY_MAX_SKEW_MS + ) + ) { + return { + chainId, + tokenAddress: normalizedAddress, + requestedTimestamp, + effectiveTimestamp: coingeckoHistorical.effectiveTimestamp.toISOString(), + priceUsd: coingeckoHistorical.priceUsd, + source: 'coingecko_history', + }; + } + } + + const marketData = await marketDataRepo.getMarketData(chainId, resolution.lookupAddress); + if (marketData?.priceUsd != null) { + return { + chainId, + tokenAddress: normalizedAddress, + requestedTimestamp, + effectiveTimestamp: marketData.lastUpdated instanceof Date ? marketData.lastUpdated.toISOString() : new Date(marketData.lastUpdated).toISOString(), + priceUsd: marketData.priceUsd, + source: 'current_market_fallback', + }; + } + + const canonicalPricing = resolveUsdValuation({ + chainId, + normalizedAddress: resolution.lookupAddress, + indexer: null, + coingecko: undefined, + cmc: undefined, + dexscreener: undefined, + }); + + if (canonicalPricing.priceUsd != null) { + return { + chainId, + tokenAddress: normalizedAddress, + requestedTimestamp, + effectiveTimestamp: canonicalPricing.asOf, + priceUsd: canonicalPricing.priceUsd, + source: 'canonical_fallback', + }; + } + + return { + chainId, + tokenAddress: normalizedAddress, + requestedTimestamp, + source: 'unavailable', + }; +} + +async function resolveCurrentPriceSnapshot( + chainId: number, + address: string +): Promise { + const normalizedAddress = address.toLowerCase(); + const resolution = resolveCanonicalQuoteAddress(chainId, normalizedAddress); + const marketData = await marketDataRepo.getMarketData(chainId, resolution.lookupAddress); + const pricing = resolveUsdValuation({ + chainId, + normalizedAddress: resolution.lookupAddress, + indexer: marketData, + coingecko: undefined, + cmc: undefined, + dexscreener: undefined, + }); + + const asOf = pricing.asOf + || (marketData?.lastUpdated instanceof Date + ? marketData.lastUpdated.toISOString() + : marketData?.lastUpdated + ? new Date(marketData.lastUpdated).toISOString() + : undefined); + + return { + chainId, + tokenAddress: normalizedAddress, + priceUsd: pricing.priceUsd, + asOf, + sourceLayer: pricing.sourceLayer, + stale: pricing.stale, + marketLastUpdated: + marketData?.lastUpdated instanceof Date + ? marketData.lastUpdated.toISOString() + : marketData?.lastUpdated + ? new Date(marketData.lastUpdated).toISOString() + : undefined, + }; +} + async function getTokensWithFallback( chainId: number, limit: number, @@ -250,7 +488,7 @@ router.get('/tokens', cacheMiddleware(60 * 1000), async (req: Request, res: Resp router.get('/tokens/:address', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => { try { const chainId = parseInt(req.query.chainId as string, 10); - const address = req.params.address; + const address = firstString(req.params.address) || ''; if (!chainId) { return res.status(400).json({ error: 'chainId is required' }); @@ -338,7 +576,7 @@ router.get('/tokens/:address', cacheMiddleware(60 * 1000), async (req: Request, router.get('/tokens/:address/pools', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => { try { const chainId = parseInt(req.query.chainId as string, 10); - const address = req.params.address; + const address = firstString(req.params.address) || ''; if (!chainId) { return res.status(400).json({ error: 'chainId is required' }); @@ -404,11 +642,13 @@ router.get('/tokens/:address/pools', cacheMiddleware(60 * 1000), async (req: Req router.get('/tokens/:address/ohlcv', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => { try { const chainId = parseInt(req.query.chainId as string, 10); - const address = req.params.address; - const interval = (req.query.interval as string) || '1h'; - const from = req.query.from ? new Date(req.query.from as string) : new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); - const to = req.query.to ? new Date(req.query.to as string) : new Date(); - const poolAddress = req.query.poolAddress as string | undefined; + const address = firstString(req.params.address) || ''; + const interval = firstString(req.query.interval as string | string[] | undefined) || '1h'; + const fromRaw = firstString(req.query.from as string | string[] | undefined); + const toRaw = firstString(req.query.to as string | string[] | undefined); + const from = fromRaw ? new Date(fromRaw) : new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); + const to = toRaw ? new Date(toRaw) : new Date(); + const poolAddress = firstString(req.query.poolAddress as string | string[] | undefined); if (!chainId) { return res.status(400).json({ error: 'chainId is required' }); @@ -439,10 +679,83 @@ router.get('/tokens/:address/ohlcv', cacheMiddleware(5 * 60 * 1000), async (req: } }); +router.get('/tokens/:address/price-at', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => { + try { + const chainId = parseInt(req.query.chainId as string, 10); + const address = firstString(req.params.address) || ''; + const rawTimestamp = String(firstString(req.query.timestamp as string | string[] | undefined) || '').trim(); + + if (!chainId) { + return res.status(400).json({ error: 'chainId is required' }); + } + + if (!rawTimestamp) { + return res.status(400).json({ error: 'timestamp is required' }); + } + + const timestamp = new Date(rawTimestamp); + if (Number.isNaN(timestamp.getTime())) { + return res.status(400).json({ error: 'timestamp must be a valid ISO-8601 datetime' }); + } + + const historicalPrice = await resolveHistoricalPriceAt(chainId, address, timestamp); + res.json(historicalPrice); + } catch (error) { + logger.error('Error fetching historical token price:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +router.get('/tokens/:address/pricing-context', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => { + try { + const chainId = parseInt(req.query.chainId as string, 10); + const address = firstString(req.params.address) || ''; + const rawTimestamp = String(firstString(req.query.timestamp as string | string[] | undefined) || '').trim(); + + if (!chainId) { + return res.status(400).json({ error: 'chainId is required' }); + } + + const token = await getTokenWithFallback(chainId, address.toLowerCase()); + if (!token) { + return res.status(404).json({ error: 'Token not found' }); + } + + const current = await resolveCurrentPriceSnapshot(chainId, address); + let historical: (HistoricalPriceSnapshot & { locked: boolean }) | undefined; + + if (rawTimestamp) { + const timestamp = new Date(rawTimestamp); + if (Number.isNaN(timestamp.getTime())) { + return res.status(400).json({ error: 'timestamp must be a valid ISO-8601 datetime' }); + } + + const historicalPrice = await resolveHistoricalPriceAt(chainId, address, timestamp); + historical = { + ...historicalPrice, + locked: + historicalPrice.source.startsWith('ohlcv_') || + historicalPrice.source === 'swap_event' || + historicalPrice.source === 'coingecko_history', + }; + } + + return res.json({ + chainId, + tokenAddress: address.toLowerCase(), + current, + historical, + }); + } catch (error) { + logger.error('Error fetching pricing context:', error); + return res.status(500).json({ error: 'Internal server error' }); + } +}); + router.get('/tokens/:address/signals', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => { try { const chainId = parseInt(req.query.chainId as string, 10); - const address = req.params.address; + const address = firstString(req.params.address) || ''; if (!chainId) { return res.status(400).json({ error: 'chainId is required' }); @@ -466,7 +779,7 @@ router.get('/tokens/:address/signals', cacheMiddleware(5 * 60 * 1000), async (re router.get('/search', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => { try { const chainId = parseInt(req.query.chainId as string, 10); - const query = req.query.q as string; + const query = firstString(req.query.q as string | string[] | undefined) || ''; if (!chainId || !query) { return res.status(400).json({ error: 'chainId and q (query) are required' }); @@ -488,7 +801,7 @@ router.get('/search', cacheMiddleware(60 * 1000), async (req: Request, res: Resp router.get('/pools/:poolAddress', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => { try { const chainId = parseInt(req.query.chainId as string, 10); - const poolAddress = req.params.poolAddress; + const poolAddress = firstString(req.params.poolAddress) || ''; if (!chainId) { return res.status(400).json({ error: 'chainId is required' }); diff --git a/services/token-aggregation/src/backfill-historical-pricing.ts b/services/token-aggregation/src/backfill-historical-pricing.ts new file mode 100644 index 0000000..470df79 --- /dev/null +++ b/services/token-aggregation/src/backfill-historical-pricing.ts @@ -0,0 +1,49 @@ +import * as dotenv from 'dotenv'; +import path from 'path'; +import { existsSync } from 'fs'; +import { HistoricalPricingBackfillService } from './services/historical-pricing-backfill'; + +const rootEnvCandidates = [ + path.resolve(__dirname, '../../.env'), + path.resolve(__dirname, '../../../.env'), +]; + +for (const candidate of rootEnvCandidates) { + if (existsSync(candidate)) { + dotenv.config({ path: candidate }); + break; + } +} + +dotenv.config(); + +function readInt(name: string, fallback: number): number { + const raw = String(process.env[name] || '').trim(); + if (!raw) return fallback; + const parsed = Number(raw); + return Number.isFinite(parsed) ? parsed : fallback; +} + +async function main(): Promise { + const chainId = readInt('BACKFILL_CHAIN_ID', 138); + const days = readInt('BACKFILL_DAYS', 30); + const chunkSize = readInt('BACKFILL_CHUNK_SIZE', 2500); + const poolLimit = readInt('BACKFILL_POOL_LIMIT', 500); + + const service = new HistoricalPricingBackfillService(); + const summary = await service.backfillChain({ + chainId, + days, + chunkSize, + poolLimit, + }); + + // eslint-disable-next-line no-console + console.log(JSON.stringify(summary, null, 2)); +} + +main().catch((error) => { + // eslint-disable-next-line no-console + console.error(error); + process.exit(1); +}); diff --git a/services/token-aggregation/src/database/repositories/swap-event-repo.ts b/services/token-aggregation/src/database/repositories/swap-event-repo.ts new file mode 100644 index 0000000..09ffb0d --- /dev/null +++ b/services/token-aggregation/src/database/repositories/swap-event-repo.ts @@ -0,0 +1,99 @@ +import { Pool } from 'pg'; +import { getDatabasePool } from '../client'; + +export interface SwapEventRecord { + chainId: number; + poolAddress: string; + transactionHash: string; + blockNumber: number; + logIndex: number; + token0Address: string; + token1Address: string; + amount0In: string; + amount1In: string; + amount0Out: string; + amount1Out: string; + amountUsd?: number; + priceUsd?: number; + token0PriceUsd?: number; + token1PriceUsd?: number; + sender?: string; + toAddress?: string; + timestamp: Date; +} + +export class SwapEventRepository { + private pool: Pool; + + constructor() { + this.pool = getDatabasePool(); + } + + private isMissingRelationError(error: unknown): boolean { + if (!error || typeof error !== 'object') { + return false; + } + + const code = (error as { code?: string }).code; + const message = (error as { message?: string }).message || ''; + return code === '42P01' || (message.includes('relation "') && message.includes('" does not exist')); + } + + async upsertSwapEvent(event: SwapEventRecord): Promise { + try { + await this.pool.query( + `INSERT INTO swap_events ( + chain_id, pool_address, transaction_hash, block_number, log_index, + token0_address, token1_address, amount0_in, amount1_in, amount0_out, amount1_out, + amount_usd, price_usd, token0_price_usd, token1_price_usd, sender, to_address, timestamp + ) + VALUES ( + $1, $2, $3, $4, $5, + $6, $7, $8, $9, $10, $11, + $12, $13, $14, $15, $16, $17, $18 + ) + ON CONFLICT (chain_id, transaction_hash, log_index) DO UPDATE SET + pool_address = EXCLUDED.pool_address, + block_number = EXCLUDED.block_number, + token0_address = EXCLUDED.token0_address, + token1_address = EXCLUDED.token1_address, + amount0_in = EXCLUDED.amount0_in, + amount1_in = EXCLUDED.amount1_in, + amount0_out = EXCLUDED.amount0_out, + amount1_out = EXCLUDED.amount1_out, + amount_usd = EXCLUDED.amount_usd, + price_usd = EXCLUDED.price_usd, + token0_price_usd = EXCLUDED.token0_price_usd, + token1_price_usd = EXCLUDED.token1_price_usd, + sender = EXCLUDED.sender, + to_address = EXCLUDED.to_address, + timestamp = EXCLUDED.timestamp`, + [ + event.chainId, + event.poolAddress.toLowerCase(), + event.transactionHash.toLowerCase(), + event.blockNumber, + event.logIndex, + event.token0Address.toLowerCase(), + event.token1Address.toLowerCase(), + event.amount0In, + event.amount1In, + event.amount0Out, + event.amount1Out, + event.amountUsd, + event.priceUsd, + event.token0PriceUsd, + event.token1PriceUsd, + event.sender?.toLowerCase(), + event.toAddress?.toLowerCase(), + event.timestamp, + ] + ); + } catch (error) { + if (this.isMissingRelationError(error)) { + return; + } + throw error; + } + } +} diff --git a/services/token-aggregation/src/indexer/chain-indexer.ts b/services/token-aggregation/src/indexer/chain-indexer.ts index dbcad4e..4343934 100644 --- a/services/token-aggregation/src/indexer/chain-indexer.ts +++ b/services/token-aggregation/src/indexer/chain-indexer.ts @@ -26,6 +26,7 @@ export class ChainIndexer { dexscreener: DexScreenerAdapter; }; private isRunning: boolean = false; + private isIndexing: boolean = false; private indexingInterval?: NodeJS.Timeout; constructor(chainId: number) { @@ -94,6 +95,12 @@ export class ChainIndexer { * Index all data (pools, tokens, market data) */ private async indexAll(): Promise { + if (this.isIndexing) { + logger.info(`Skipping overlapping index cycle for chain ${this.chainId}`); + return; + } + + this.isIndexing = true; try { // 1. Index pools logger.info(`Indexing pools for chain ${this.chainId}...`); @@ -134,6 +141,8 @@ export class ChainIndexer { } catch (error) { logger.error(`Error in indexAll for chain ${this.chainId}:`, error); throw error; + } finally { + this.isIndexing = false; } } diff --git a/services/token-aggregation/src/indexer/cross-chain-indexer.ts b/services/token-aggregation/src/indexer/cross-chain-indexer.ts index bc28135..b6bfcf6 100644 --- a/services/token-aggregation/src/indexer/cross-chain-indexer.ts +++ b/services/token-aggregation/src/indexer/cross-chain-indexer.ts @@ -124,13 +124,18 @@ async function queryFilterWithRangeFallback( } const logs: ethers.EventLog[] = []; - for ( - let start = fromBlock; - start <= toBlock; - start += CROSS_CHAIN_QUERY_FALLBACK_BLOCK_SPAN - ) { - const end = Math.min(start + CROSS_CHAIN_QUERY_FALLBACK_BLOCK_SPAN - 1, toBlock); - const chunk = (await contract.queryFilter(filter as never, start, end)) as ethers.EventLog[]; + const totalSpan = toBlock - fromBlock + 1; + const chunkSpan = Math.min(CROSS_CHAIN_QUERY_FALLBACK_BLOCK_SPAN, totalSpan); + + for (let start = fromBlock; start <= toBlock; start += chunkSpan) { + const end = Math.min(start + chunkSpan - 1, toBlock); + if (start === fromBlock && end === toBlock) { + const mid = Math.floor((start + end) / 2); + logs.push(...await queryFilterWithRangeFallback(contract, filter, start, mid)); + logs.push(...await queryFilterWithRangeFallback(contract, filter, mid + 1, end)); + continue; + } + const chunk = await queryFilterWithRangeFallback(contract, filter, start, end); logs.push(...chunk); } return logs; diff --git a/services/token-aggregation/src/indexer/ohlcv-generator.ts b/services/token-aggregation/src/indexer/ohlcv-generator.ts index 4af3be3..5c8753b 100644 --- a/services/token-aggregation/src/indexer/ohlcv-generator.ts +++ b/services/token-aggregation/src/indexer/ohlcv-generator.ts @@ -73,8 +73,14 @@ export class OHLCVGenerator { const intervalMs = this.getIntervalMs(interval); // Get swap events for the time range + const priceColumn = `CASE + WHEN token0_address = $2 THEN COALESCE(token0_price_usd, price_usd) + WHEN token1_address = $2 THEN COALESCE(token1_price_usd, price_usd) + ELSE price_usd + END AS token_price_usd`; + let query = ` - SELECT timestamp, amount_usd, price_usd + SELECT timestamp, amount_usd, ${priceColumn} FROM swap_events WHERE chain_id = $1 AND (token0_address = $2 OR token1_address = $2) @@ -102,7 +108,7 @@ export class OHLCVGenerator { result.rows.forEach((row) => { const timestamp = new Date(row.timestamp); const intervalStart = Math.floor(timestamp.getTime() / intervalMs) * intervalMs; - const price = parseFloat(row.price_usd || '0'); + const price = parseFloat((row.token_price_usd ?? row.price_usd ?? '0').toString()); const volume = parseFloat(row.amount_usd || '0'); if (!intervals.has(intervalStart)) { diff --git a/services/token-aggregation/src/indexer/pool-indexer.ts b/services/token-aggregation/src/indexer/pool-indexer.ts index 3da2b3e..d4b552f 100644 --- a/services/token-aggregation/src/indexer/pool-indexer.ts +++ b/services/token-aggregation/src/indexer/pool-indexer.ts @@ -50,6 +50,25 @@ const DODO_PMM_INTEGRATION_ABI = [ 'function getPoolPriceOrOracle(address) view returns (uint256 price)', ]; +const POOL_QUERY_FALLBACK_BLOCK_SPAN = Math.max( + 1, + Number(process.env.POOL_QUERY_FALLBACK_BLOCK_SPAN || 5000) +); + +function isRpcRangeLimitError(error: unknown): boolean { + const parts = [ + typeof error === 'object' && error ? (error as { message?: string }).message : '', + typeof error === 'object' && error ? (error as { shortMessage?: string }).shortMessage : '', + typeof error === 'object' && error + ? ((error as { error?: { message?: string } }).error?.message ?? '') + : '', + ]; + + return parts.some((part) => + typeof part === 'string' && /range limit|exceeds maximum rpc range/i.test(part) + ); +} + export class PoolIndexer { private static missingDexConfigLogged = new Set(); private static staleDodoPoolsLogged = new Set(); @@ -75,6 +94,37 @@ export class PoolIndexer { pools.push(pool); } + private async queryFilterWithRangeFallback( + contract: ethers.Contract, + filter: ReturnType, + fromBlock: number, + toBlock: number + ): Promise { + try { + return (await contract.queryFilter(filter as never, fromBlock, toBlock)) as ethers.EventLog[]; + } catch (error) { + if (!isRpcRangeLimitError(error) || fromBlock >= toBlock) { + throw error; + } + } + + const logs: ethers.EventLog[] = []; + const totalSpan = toBlock - fromBlock + 1; + const chunkSpan = Math.min(POOL_QUERY_FALLBACK_BLOCK_SPAN, totalSpan); + + for (let start = fromBlock; start <= toBlock; start += chunkSpan) { + const end = Math.min(start + chunkSpan - 1, toBlock); + if (start === fromBlock && end === toBlock) { + const mid = Math.floor((start + end) / 2); + logs.push(...await this.queryFilterWithRangeFallback(contract, filter, start, mid)); + logs.push(...await this.queryFilterWithRangeFallback(contract, filter, mid + 1, end)); + continue; + } + logs.push(...await this.queryFilterWithRangeFallback(contract, filter, start, end)); + } + return logs; + } + /** * Index all pools for configured DEX types */ @@ -230,7 +280,7 @@ export class PoolIndexer { // Listen for PairCreated events const filter = factory.filters.PairCreated(); - const events = await factory.queryFilter(filter, fromBlock, currentBlock); + const events = await this.queryFilterWithRangeFallback(factory, filter, fromBlock, currentBlock); for (const event of events) { const ev = event as ethers.EventLog; @@ -284,7 +334,7 @@ export class PoolIndexer { const fromBlock = config.startBlock || Math.max(0, currentBlock - 10000); const filter = factory.filters.PoolCreated(); - const events = await factory.queryFilter(filter, fromBlock, currentBlock); + const events = await this.queryFilterWithRangeFallback(factory, filter, fromBlock, currentBlock); for (const event of events) { const ev = event as ethers.EventLog; diff --git a/services/token-aggregation/src/services/historical-pricing-backfill.ts b/services/token-aggregation/src/services/historical-pricing-backfill.ts new file mode 100644 index 0000000..230a6c5 --- /dev/null +++ b/services/token-aggregation/src/services/historical-pricing-backfill.ts @@ -0,0 +1,489 @@ +import { ethers } from 'ethers'; +import { getChainConfig } from '../config/chains'; +import { getCanonicalPriceUsd } from './canonical-price-oracle'; +import { resolveCanonicalQuoteAddress } from '../config/canonical-tokens'; +import { TokenRepository } from '../database/repositories/token-repo'; +import { PoolRepository, type LiquidityPool } from '../database/repositories/pool-repo'; +import { MarketDataRepository } from '../database/repositories/market-data-repo'; +import { SwapEventRepository, type SwapEventRecord } from '../database/repositories/swap-event-repo'; +import { OHLCVGenerator, type OHLCVInterval } from '../indexer/ohlcv-generator'; +import { logger } from '../utils/logger'; + +const ERC20_METADATA_ABI = [ + 'function decimals() view returns (uint8)', + 'function symbol() view returns (string)', +] as const; + +const UNISWAP_V2_SWAP_IFACE = new ethers.Interface([ + 'event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to)', +]); + +const UNISWAP_V3_SWAP_IFACE = new ethers.Interface([ + 'event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)', +]); + +const DODO_SWAP_IFACE = new ethers.Interface([ + 'event DODOSwap(address fromToken, address toToken, uint256 fromAmount, uint256 toAmount, address trader, address receiver)', +]); + +type BackfillOptions = { + chainId: number; + days?: number; + chunkSize?: number; + poolLimit?: number; +}; + +type TokenMetadata = { + address: string; + decimals: number; + symbol?: string; +}; + +type BackfillSummary = { + chainId: number; + poolsScanned: number; + poolsProcessed: number; + swapsUpserted: number; + ohlcvTokensGenerated: number; + fromBlock: number; + toBlock: number; + days: number; +}; + +export class HistoricalPricingBackfillService { + private tokenRepo = new TokenRepository(); + private poolRepo = new PoolRepository(); + private marketDataRepo = new MarketDataRepository(); + private swapEventRepo = new SwapEventRepository(); + private ohlcvGenerator = new OHLCVGenerator(); + private providerCache = new Map(); + private tokenMetadataCache = new Map(); + private anchorPriceCache = new Map(); + private blockTimestampCache = new Map(); + + async backfillChain(options: BackfillOptions): Promise { + const chainId = options.chainId; + const days = Math.max(1, options.days ?? 30); + const chunkSize = Math.max(100, options.chunkSize ?? 2500); + const config = getChainConfig(chainId); + if (!config) { + throw new Error(`Chain ${chainId} is not configured`); + } + + const provider = this.getProvider(chainId); + const currentBlock = await provider.getBlockNumber(); + const blocksPerDay = Math.max(1, Math.ceil((24 * 60 * 60) / Math.max(config.blockTime, 1))); + const defaultFromBlock = Math.max(0, currentBlock - (blocksPerDay * days)); + const pools = await this.poolRepo.getPoolsByChain(chainId, options.poolLimit ?? 500); + const tokenAddresses = new Set(); + + let poolsProcessed = 0; + let swapsUpserted = 0; + + for (const pool of pools) { + tokenAddresses.add(pool.token0Address.toLowerCase()); + tokenAddresses.add(pool.token1Address.toLowerCase()); + + if (!this.isSupportedDexType(pool.dexType)) { + continue; + } + + const fromBlock = Math.max(defaultFromBlock, Number(pool.createdAtBlock || 0)); + const count = await this.backfillPool(pool, fromBlock, currentBlock, chunkSize); + if (count > 0) { + poolsProcessed += 1; + swapsUpserted += count; + } + } + + const now = new Date(); + const from = new Date(now.getTime() - days * 24 * 60 * 60 * 1000); + let ohlcvTokensGenerated = 0; + const intervals: OHLCVInterval[] = ['5m', '15m', '1h', '4h', '24h']; + + for (const tokenAddress of tokenAddresses) { + for (const interval of intervals) { + await this.ohlcvGenerator.generateAndStore(chainId, tokenAddress, interval, from, now); + } + ohlcvTokensGenerated += 1; + } + + return { + chainId, + poolsScanned: pools.length, + poolsProcessed, + swapsUpserted, + ohlcvTokensGenerated, + fromBlock: defaultFromBlock, + toBlock: currentBlock, + days, + }; + } + + private isSupportedDexType(dexType: string): boolean { + return ['uniswap_v2', 'sushiswap', 'uniswap_v3', 'dodo'].includes(dexType); + } + + private getProvider(chainId: number): ethers.JsonRpcProvider { + const cached = this.providerCache.get(chainId); + if (cached) { + return cached; + } + + const config = getChainConfig(chainId); + if (!config) { + throw new Error(`Chain ${chainId} is not configured`); + } + + const provider = new ethers.JsonRpcProvider(config.rpcUrl); + this.providerCache.set(chainId, provider); + return provider; + } + + private async backfillPool( + pool: LiquidityPool, + fromBlock: number, + toBlock: number, + chunkSize: number + ): Promise { + let count = 0; + const provider = this.getProvider(pool.chainId); + + for (let start = fromBlock; start <= toBlock; start += chunkSize) { + const end = Math.min(toBlock, start + chunkSize - 1); + const logs = await this.fetchPoolLogs(pool, provider, start, end); + + for (const log of logs) { + const event = await this.toSwapEventRecord(pool, log, provider); + if (!event) { + continue; + } + + await this.swapEventRepo.upsertSwapEvent(event); + count += 1; + } + } + + logger.info('Historical pricing pool backfill complete', { + chainId: pool.chainId, + poolAddress: pool.poolAddress, + dexType: pool.dexType, + fromBlock, + toBlock, + swapsUpserted: count, + }); + + return count; + } + + private async fetchPoolLogs( + pool: LiquidityPool, + provider: ethers.JsonRpcProvider, + fromBlock: number, + toBlock: number + ): Promise { + let iface: ethers.Interface; + let topic: string; + + switch (pool.dexType) { + case 'uniswap_v2': + case 'sushiswap': + iface = UNISWAP_V2_SWAP_IFACE; + topic = UNISWAP_V2_SWAP_IFACE.getEvent('Swap')!.topicHash; + break; + case 'uniswap_v3': + iface = UNISWAP_V3_SWAP_IFACE; + topic = UNISWAP_V3_SWAP_IFACE.getEvent('Swap')!.topicHash; + break; + case 'dodo': + iface = DODO_SWAP_IFACE; + topic = DODO_SWAP_IFACE.getEvent('DODOSwap')!.topicHash; + break; + default: + return []; + } + + try { + return await provider.getLogs({ + address: pool.poolAddress, + fromBlock, + toBlock, + topics: [topic], + }); + } catch (error) { + logger.warn('Historical pricing log fetch failed for pool chunk', { + chainId: pool.chainId, + poolAddress: pool.poolAddress, + dexType: pool.dexType, + fromBlock, + toBlock, + error, + }); + return []; + } + } + + private async toSwapEventRecord( + pool: LiquidityPool, + log: ethers.Log, + provider: ethers.JsonRpcProvider + ): Promise { + try { + if (pool.dexType === 'uniswap_v2' || pool.dexType === 'sushiswap') { + const parsed = UNISWAP_V2_SWAP_IFACE.parseLog(log)!; + const amount0In = BigInt(parsed.args.amount0In.toString()); + const amount1In = BigInt(parsed.args.amount1In.toString()); + const amount0Out = BigInt(parsed.args.amount0Out.toString()); + const amount1Out = BigInt(parsed.args.amount1Out.toString()); + return await this.buildSwapEventRecord(pool, log, provider, { + amount0In, + amount1In, + amount0Out, + amount1Out, + sender: String(parsed.args.sender), + toAddress: String(parsed.args.to), + }); + } + + if (pool.dexType === 'uniswap_v3') { + const parsed = UNISWAP_V3_SWAP_IFACE.parseLog(log)!; + const amount0Signed = BigInt(parsed.args.amount0.toString()); + const amount1Signed = BigInt(parsed.args.amount1.toString()); + return await this.buildSwapEventRecord(pool, log, provider, { + amount0In: amount0Signed > 0n ? amount0Signed : 0n, + amount1In: amount1Signed > 0n ? amount1Signed : 0n, + amount0Out: amount0Signed < 0n ? -amount0Signed : 0n, + amount1Out: amount1Signed < 0n ? -amount1Signed : 0n, + sender: String(parsed.args.sender), + toAddress: String(parsed.args.recipient), + }); + } + + if (pool.dexType === 'dodo') { + const parsed = DODO_SWAP_IFACE.parseLog(log)!; + const fromToken = String(parsed.args.fromToken).toLowerCase(); + const toToken = String(parsed.args.toToken).toLowerCase(); + const fromAmount = BigInt(parsed.args.fromAmount.toString()); + const toAmount = BigInt(parsed.args.toAmount.toString()); + + if (fromToken === pool.token0Address.toLowerCase() && toToken === pool.token1Address.toLowerCase()) { + return await this.buildSwapEventRecord(pool, log, provider, { + amount0In: fromAmount, + amount1In: 0n, + amount0Out: 0n, + amount1Out: toAmount, + sender: String(parsed.args.trader), + toAddress: String(parsed.args.receiver), + }); + } + + if (fromToken === pool.token1Address.toLowerCase() && toToken === pool.token0Address.toLowerCase()) { + return await this.buildSwapEventRecord(pool, log, provider, { + amount0In: 0n, + amount1In: fromAmount, + amount0Out: toAmount, + amount1Out: 0n, + sender: String(parsed.args.trader), + toAddress: String(parsed.args.receiver), + }); + } + } + } catch (error) { + logger.warn('Historical pricing log parse failed', { + chainId: pool.chainId, + poolAddress: pool.poolAddress, + dexType: pool.dexType, + txHash: log.transactionHash, + error, + }); + } + + return null; + } + + private async buildSwapEventRecord( + pool: LiquidityPool, + log: ethers.Log, + provider: ethers.JsonRpcProvider, + amounts: { + amount0In: bigint; + amount1In: bigint; + amount0Out: bigint; + amount1Out: bigint; + sender?: string; + toAddress?: string; + } + ): Promise { + const moved0 = amounts.amount0In > 0n ? amounts.amount0In : amounts.amount0Out; + const moved1 = amounts.amount1In > 0n ? amounts.amount1In : amounts.amount1Out; + const valuation = await this.deriveSwapValuation(pool.chainId, pool.token0Address, pool.token1Address, moved0, moved1); + + return { + chainId: pool.chainId, + poolAddress: pool.poolAddress, + transactionHash: log.transactionHash, + blockNumber: Number(log.blockNumber), + logIndex: log.index, + token0Address: pool.token0Address, + token1Address: pool.token1Address, + amount0In: amounts.amount0In.toString(), + amount1In: amounts.amount1In.toString(), + amount0Out: amounts.amount0Out.toString(), + amount1Out: amounts.amount1Out.toString(), + amountUsd: valuation.amountUsd, + priceUsd: valuation.priceUsd, + token0PriceUsd: valuation.token0PriceUsd, + token1PriceUsd: valuation.token1PriceUsd, + sender: amounts.sender, + toAddress: amounts.toAddress, + timestamp: await this.getBlockTimestamp(provider, pool.chainId, Number(log.blockNumber)), + }; + } + + private async deriveSwapValuation( + chainId: number, + token0Address: string, + token1Address: string, + amount0Raw: bigint, + amount1Raw: bigint + ): Promise<{ + amountUsd?: number; + priceUsd?: number; + token0PriceUsd?: number; + token1PriceUsd?: number; + }> { + if (amount0Raw <= 0n || amount1Raw <= 0n) { + return {}; + } + + const [token0Meta, token1Meta] = await Promise.all([ + this.getTokenMetadata(chainId, token0Address), + this.getTokenMetadata(chainId, token1Address), + ]); + + const amount0 = Number(ethers.formatUnits(amount0Raw, token0Meta.decimals)); + const amount1 = Number(ethers.formatUnits(amount1Raw, token1Meta.decimals)); + + if (!Number.isFinite(amount0) || !Number.isFinite(amount1) || amount0 <= 0 || amount1 <= 0) { + return {}; + } + + const [anchor0, anchor1] = await Promise.all([ + this.resolveAnchorUsdPrice(chainId, token0Address), + this.resolveAnchorUsdPrice(chainId, token1Address), + ]); + + let token0PriceUsd = anchor0; + let token1PriceUsd = anchor1; + + if (anchor1 != null && anchor1 > 0) { + token0PriceUsd = (amount1 * anchor1) / amount0; + } + + if (anchor0 != null && anchor0 > 0) { + token1PriceUsd = (amount0 * anchor0) / amount1; + } + + if ((token0PriceUsd == null || token0PriceUsd <= 0) && token1PriceUsd != null && token1PriceUsd > 0) { + token0PriceUsd = (amount1 * token1PriceUsd) / amount0; + } + + if ((token1PriceUsd == null || token1PriceUsd <= 0) && token0PriceUsd != null && token0PriceUsd > 0) { + token1PriceUsd = (amount0 * token0PriceUsd) / amount1; + } + + const amountUsd = + token0PriceUsd != null && token0PriceUsd > 0 + ? amount0 * token0PriceUsd + : token1PriceUsd != null && token1PriceUsd > 0 + ? amount1 * token1PriceUsd + : undefined; + + return { + amountUsd, + priceUsd: token0PriceUsd ?? token1PriceUsd, + token0PriceUsd, + token1PriceUsd, + }; + } + + private async resolveAnchorUsdPrice(chainId: number, address: string): Promise { + const normalized = `${chainId}:${address.toLowerCase()}`; + if (this.anchorPriceCache.has(normalized)) { + return this.anchorPriceCache.get(normalized); + } + + const canonical = getCanonicalPriceUsd(chainId, address); + if (canonical != null && canonical > 0) { + this.anchorPriceCache.set(normalized, canonical); + return canonical; + } + + const resolution = resolveCanonicalQuoteAddress(chainId, address.toLowerCase()); + const marketData = await this.marketDataRepo.getMarketData(chainId, resolution.lookupAddress); + const price = marketData?.priceUsd; + this.anchorPriceCache.set(normalized, price); + return price; + } + + private async getTokenMetadata(chainId: number, address: string): Promise { + const normalized = `${chainId}:${address.toLowerCase()}`; + const cached = this.tokenMetadataCache.get(normalized); + if (cached) { + return cached; + } + + const token = await this.tokenRepo.getToken(chainId, address.toLowerCase()); + if (token?.decimals != null) { + const metadata = { + address: address.toLowerCase(), + decimals: token.decimals, + symbol: token.symbol, + }; + this.tokenMetadataCache.set(normalized, metadata); + return metadata; + } + + const provider = this.getProvider(chainId); + const contract = new ethers.Contract(address, ERC20_METADATA_ABI, provider); + + try { + const [decimals, symbol] = await Promise.all([ + contract.decimals().catch(() => 18), + contract.symbol().catch(() => undefined), + ]); + + const metadata = { + address: address.toLowerCase(), + decimals: Number(decimals), + symbol: typeof symbol === 'string' ? symbol : undefined, + }; + this.tokenMetadataCache.set(normalized, metadata); + return metadata; + } catch { + const metadata = { + address: address.toLowerCase(), + decimals: 18, + }; + this.tokenMetadataCache.set(normalized, metadata); + return metadata; + } + } + + private async getBlockTimestamp( + provider: ethers.JsonRpcProvider, + chainId: number, + blockNumber: number + ): Promise { + const key = `${chainId}:${blockNumber}`; + const cached = this.blockTimestampCache.get(key); + if (cached) { + return cached; + } + + const block = await provider.getBlock(blockNumber); + const timestamp = new Date(Number(block?.timestamp || 0) * 1000); + this.blockTimestampCache.set(key, timestamp); + return timestamp; + } +}