diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..f8538912 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +PORT = 3002 +RPC_WSS_URL = "" +DATABASE_URL = "" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0ec220b4..22767925 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2372 +1,2574 @@ { - "name": "eigen-explorer-backend", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "eigen-explorer-backend", - "version": "1.0.0", - "license": "Apache-2.0", - "workspaces": [ - "packages/*" - ], - "dependencies": { - "route-cache": "^0.7.0" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.10.0", - "license": "MIT" - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", - "dev": true - }, - "node_modules/@noble/curves": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.2", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@prisma/client": { - "version": "5.11.0", - "hasInstallScript": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "prisma": "*" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } - } - }, - "node_modules/@prisma/debug": { - "version": "5.11.0", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "5.11.0", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.11.0", - "@prisma/engines-version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", - "@prisma/fetch-engine": "5.11.0", - "@prisma/get-platform": "5.11.0" - } - }, - "node_modules/@prisma/engines-version": { - "version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine": { - "version": "5.11.0", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.11.0", - "@prisma/engines-version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", - "@prisma/get-platform": "5.11.0" - } - }, - "node_modules/@prisma/get-platform": { - "version": "5.11.0", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.11.0" - } - }, - "node_modules/@scure/base": { - "version": "1.1.5", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.3.2", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.2.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "license": "BSD-3-Clause" - }, - "node_modules/@slack/logger": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", - "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", - "dependencies": { - "@types/node": ">=18.0.0" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@slack/types": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.11.0.tgz", - "integrity": "sha512-UlIrDWvuLaDly3QZhCPnwUSI/KYmV1N9LyhuH6EDKCRS1HWZhyTG3Ja46T3D0rYfqdltKYFXbJSSRPwZpwO0cQ==", - "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" - } - }, - "node_modules/@slack/web-api": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.0.4.tgz", - "integrity": "sha512-21tbte7N8itwjG7nsiQbDmXP9T/oqEILuvyL2UtgaZxfSY4a1JWWsLGL5n/hcgS2WE2oxmEHsBuhuRkZDwDovw==", - "dependencies": { - "@slack/logger": "^4.0.0", - "@slack/types": "^2.9.0", - "@types/node": ">=18.0.0", - "@types/retry": "0.12.0", - "axios": "^1.6.5", - "eventemitter3": "^5.0.1", - "form-data": "^4.0.0", - "is-electron": "2.2.2", - "is-stream": "^2", - "p-queue": "^6", - "p-retry": "^4", - "retry": "^0.13.1" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cookie-parser": { - "version": "1.4.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.21", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-QjCOmf5kYwekcsfEKhcEHMK8/SvgnneuSDXFERBuC/DPca2KJIO/gpChTsVb35BoWLBpEAJWz1GFVEArSdtKtw==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/morgan": { - "version": "1.9.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ms": { - "version": "0.7.34", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.12.2", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/qs": { - "version": "6.9.14", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, - "node_modules/@types/route-cache": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@types/route-cache/-/route-cache-0.5.5.tgz", - "integrity": "sha512-HJYRyjX10iV8u9mhl1oiDX5SLm8ldJuFeRWyi0FoeOoX7aVa3L7XHLVm+p+D5i9PCnNElIbbjU6tfM4+eHshyA==", - "dev": true, - "dependencies": { - "@types/express": "*", - "@types/lru-cache": "^4.1.3", - "@types/node": "*", - "ioredis": "^5.3.2" - } - }, - "node_modules/@types/send": { - "version": "0.17.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "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==", - "dev": true - }, - "node_modules/abitype": { - "version": "1.0.0", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/basic-auth": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "1.20.2", - "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.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/http-errors/node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/body-parser/node_modules/http-errors/node_modules/setprototypeof": { - "version": "1.2.0", - "license": "ISC" - }, - "node_modules/body-parser/node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/character-parser": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.4.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.6", - "license": "MIT", - "dependencies": { - "cookie": "0.4.1", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dotenv": { - "version": "16.4.5", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/eigen-explorer-backend": { - "resolved": "packages/api", - "link": true - }, - "node_modules/eigen-explorer-backend-db": { - "resolved": "packages/prisma", - "link": true - }, - "node_modules/eigen-explorer-backend-monitor": { - "resolved": "packages/monitor", - "link": true - }, - "node_modules/eigen-explorer-backend-seeder": { - "resolved": "packages/seeder", - "link": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.19.12", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/express": { - "version": "4.19.2", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/http-errors": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/http-errors/node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/setprototypeof": { - "version": "1.2.0", - "license": "ISC" - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/statuses": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.7.3", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "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, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/helmet": { - "version": "7.1.0", - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/http-errors": { - "version": "1.6.3", - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/depd": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/setprototypeof": { - "version": "1.1.0", - "license": "ISC" - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "1.4.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/inherits": { - "version": "2.0.3", - "license": "ISC" - }, - "node_modules/ioredis": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", - "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", - "dev": true, - "dependencies": { - "@ioredis/commands": "^1.1.1", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, - "node_modules/ioredis/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/ioredis/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-promise": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isows": { - "version": "1.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wagmi-dev" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/jade": { - "version": "0.29.0", - "dependencies": { - "character-parser": "~1.0.0", - "commander": "0.6.1", - "mkdirp": "0.3.x", - "monocle": "~0.1.43", - "transformers": "~1.8.0" - }, - "bin": { - "jade": "bin/jade" - } - }, - "node_modules/jade/node_modules/commander": { - "version": "0.6.1", - "engines": { - "node": ">= 0.4.x" - } - }, - "node_modules/joi": { - "version": "17.12.2", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/methods": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mkdirp": { - "version": "0.3.5", - "license": "MIT" - }, - "node_modules/monocle": { - "version": "0.1.48", - "license": "BSD", - "dependencies": { - "readdirp": "~0.2.3" - } - }, - "node_modules/monocle/node_modules/readdirp": { - "version": "0.2.5", - "license": "MIT", - "dependencies": { - "minimatch": ">=0.2.4" - }, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/morgan": { - "version": "1.9.1", - "license": "MIT", - "dependencies": { - "basic-auth": "~2.0.0", - "debug": "2.6.9", - "depd": "~1.1.2", - "on-finished": "~2.3.0", - "on-headers": "~1.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/morgan/node_modules/depd": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nodemon": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", - "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/nodemon/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/openapi": { - "resolved": "packages/openapi", - "link": true - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "license": "MIT" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prisma": { - "version": "5.11.0", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/engines": "5.11.0" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/promise": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "is-promise": "~1" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/qs": { - "version": "6.11.0", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors/node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors/node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/raw-body/node_modules/http-errors/node_modules/setprototypeof": { - "version": "1.2.0", - "license": "ISC" - }, - "node_modules/raw-body/node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "dev": true, - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/route-cache": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/route-cache/-/route-cache-0.7.0.tgz", - "integrity": "sha512-M4LbH7fTVwH72+CDSv1eaeC+0JWjo427Zp7nmn4LXf31OaaRhvK2xMNTHZgVT8caLDXrzREPHW2QPzKN/ZMmEg==", - "dependencies": { - "debug": "3.1.0", - "lru-cache": "4.0.1" - } - }, - "node_modules/route-cache/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/route-cache/node_modules/lru-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz", - "integrity": "sha512-MX0ZnRoVTWXBiNe9dysqKXjvhmQgHsOirh/2rerIVJ8sbQeMxc5OPj0HDpVV3bYjdE6GTHrPf8BEHJqWHFkjHA==", - "dependencies": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "node_modules/route-cache/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/http-errors": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/http-errors/node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/send/node_modules/http-errors/node_modules/setprototypeof": { - "version": "1.2.0", - "license": "ISC" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "license": "MIT", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "dev": true - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/transformers": { - "version": "1.8.3", - "license": "MIT", - "dependencies": { - "promise": "~2.0" - } - }, - "node_modules/tsx": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.3.tgz", - "integrity": "sha512-+fQnMqIp/jxZEXLcj6WzYy9FhcS5/Dfk8y4AtzJ6ejKcKqmfTF8Gso/jtrzDggCF2zTU20gJa6n8XqPYwDAUYQ==", - "dev": true, - "dependencies": { - "esbuild": "~0.19.10", - "get-tsconfig": "^4.7.2" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "devOptional": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/undici-types": { - "version": "5.26.5", - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/viem": { - "version": "2.9.21", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.9.21.tgz", - "integrity": "sha512-8GtxPjPGpiN5cmr19zSX9mb1LX/eON3MPxxAd3QmyUFn69Rp566zlREOqE7zM35y5yX59fXwnz6O3X7e9+C9zg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "dependencies": { - "@adraffy/ens-normalize": "1.10.0", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@scure/bip32": "1.3.2", - "@scure/bip39": "1.2.1", - "abitype": "1.0.0", - "isows": "1.0.3", - "ws": "8.13.0" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.13.0", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/zod": { - "version": "3.23.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.4.tgz", - "integrity": "sha512-/AtWOKbBgjzEYYQRNfoGKHObgfAZag6qUJX1VbHo2PRBgS+wfWagEY2mizjfyAPcGesrJOcx/wcl0L9WnVrHFw==", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-error": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/zod-error/-/zod-error-1.5.0.tgz", - "integrity": "sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==", - "dependencies": { - "zod": "^3.20.2" - } - }, - "node_modules/zod-openapi": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/zod-openapi/-/zod-openapi-2.17.0.tgz", - "integrity": "sha512-S6x14LmPWZ3Sbnx71pqzIB6oIox1ce6vHnJY7ZUYLBm8TTEQ6/g6+kwutna5jplAhzphkMbYPbtW3cmHXFcRBg==", - "engines": { - "node": ">=16.11" - }, - "peerDependencies": { - "zod": "^3.21.4" - } - }, - "packages/api": { - "name": "eigen-explorer-backend", - "version": "0.0.1", - "dependencies": { - "@prisma/client": "^5.11.0", - "cookie-parser": "~1.4.4", - "cors": "^2.8.5", - "debug": "~2.6.9", - "dotenv": "^16.4.5", - "express": "^4.18.3", - "helmet": "^7.1.0", - "http-errors": "~1.6.3", - "jade": "^0.29.0", - "joi": "^17.12.2", - "morgan": "~1.9.1", - "route-cache": "^0.7.0", - "viem": "^2.8.14", - "zod": "^3.23.4", - "zod-error": "^1.5.0", - "zod-openapi": "^2.17.0" - }, - "devDependencies": { - "@types/cookie-parser": "^1.4.7", - "@types/cors": "^2.8.17", - "@types/debug": "^4.1.12", - "@types/express": "^4.17.21", - "@types/morgan": "^1.9.9", - "@types/node": "^20.12.2", - "@types/route-cache": "^0.5.5", - "prisma": "^5.11.0", - "tsx": "^4.7.1", - "typescript": "^5.4.3" - } - }, - "packages/docs": { - "name": "eigen-explorer-backend-docs", - "version": "0.0.1", - "extraneous": true, - "devDependencies": { - "@types/node": "^20.12.2", - "@types/swagger-jsdoc": "^6.0.4", - "swagger-jsdoc": "^6.2.8", - "tsx": "^4.7.1", - "typescript": "^5.4.3" - } - }, - "packages/monitor": { - "name": "eigen-explorer-backend-monitor", - "version": "0.0.1", - "dependencies": { - "@slack/web-api": "^7.0.4", - "dotenv": "^16.4.5", - "viem": "^2.9.21" - }, - "devDependencies": { - "@types/node": "^20.12.2", - "prisma": "^5.11.0", - "tsx": "^4.7.1", - "typescript": "^5.4.5" - } - }, - "packages/monitoring": { - "name": "eigen-explorer-backend-monitoring", - "version": "0.0.1", - "extraneous": true, - "dependencies": { - "@slack/web-api": "^7.0.4", - "dotenv": "^16.4.5", - "viem": "^2.9.21" - }, - "devDependencies": { - "@types/node": "^20.12.2", - "prisma": "^5.11.0", - "tsx": "^4.7.1", - "typescript": "^5.4.5" - } - }, - "packages/openapi": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "zod": "^3.23.4", - "zod-openapi": "^2.17.0" - }, - "devDependencies": { - "nodemon": "^3.1.0", - "tsx": "^4.7.3", - "typescript": "^5.4.5" - } - }, - "packages/openapi-deprecated": { - "name": "eigen-explorer-backend-docs", - "version": "0.0.1", - "extraneous": true, - "dependencies": { - "zod-openapi": "^2.17.0" - }, - "devDependencies": { - "@types/node": "^20.12.2", - "@types/swagger-jsdoc": "^6.0.4", - "swagger-jsdoc": "^6.2.8", - "tsx": "^4.7.3", - "typescript": "^5.4.3" - } - }, - "packages/prisma": { - "name": "eigen-explorer-backend-db", - "version": "0.0.1", - "devDependencies": { - "prisma": "^5.11.0" - } - }, - "packages/seeder": { - "name": "eigen-explorer-backend-seeder", - "version": "0.0.1", - "dependencies": { - "dotenv": "^16.4.5", - "viem": "^2.9.21" - }, - "devDependencies": { - "@types/node": "^20.12.2", - "prisma": "^5.11.0", - "tsx": "^4.7.1", - "typescript": "^5.4.3" - } - } - } + "name": "eigen-explorer-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "eigen-explorer-backend", + "version": "1.0.0", + "license": "Apache-2.0", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "route-cache": "^0.7.0" + }, + "devDependencies": { + "@biomejs/biome": "1.4.1" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "license": "MIT" + }, + "node_modules/@biomejs/biome": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.4.1.tgz", + "integrity": "sha512-JccVAwPbhi37pdxbAGmaOBjUTKEwEjWAhl7rKkVVuXHo4MLASXJ5HR8BTgrImi4/7rTBsGz1tgVD1Kwv1CHGRg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.4.1", + "@biomejs/cli-darwin-x64": "1.4.1", + "@biomejs/cli-linux-arm64": "1.4.1", + "@biomejs/cli-linux-x64": "1.4.1", + "@biomejs/cli-win32-arm64": "1.4.1", + "@biomejs/cli-win32-x64": "1.4.1" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.4.1.tgz", + "integrity": "sha512-PZWy2Idndqux38p6AXSDQM2ldRAWi32bvb7bMbTN0ALzpWYMYnxd71ornatumSSJYoNhKmxzDLq+jct7nZJ79w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.4.1.tgz", + "integrity": "sha512-soj3BWhnsM1M2JlzR09cibUzG1owJqetwj/Oo7yg0foijo9lNH9XWXZfJBYDKgW/6Fomn+CC2EcUS+hisQzt9g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.4.1.tgz", + "integrity": "sha512-YIZqfJUg4F+fPsBTXxgD7EU2E5OAYbmYSl/snf4PevwfQCWE/omOFZv+NnIQmjYj9I7ParDgcJvanoA3/kO0JQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.4.1.tgz", + "integrity": "sha512-9YOZw3qBd/KUj63A6Hn2zZgzGb2nbESM0qNmeMXgmqinVKM//uc4OgY5TuKITuGjMSvcVxxd4dX1IzYjV9qvNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.4.1.tgz", + "integrity": "sha512-nWQbvkNKxYn/kCQ0yVF8kCaS3VzaGvtFSmItXiMknU4521LDjJ7tNWH12Gol+pIslrCbd4E1LhJa0a3ThRsBVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.4.1.tgz", + "integrity": "sha512-88fR2CQxQ4YLs2BUDuywWYQpUKgU3A3sTezANFc/4LGKQFFLV2yX+F7QAdZVkMHfA+RD9Xg178HomM/6mnTNPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.*" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "dev": true + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@prisma/client": { + "version": "5.11.0", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.11.0", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.11.0", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.11.0", + "@prisma/engines-version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", + "@prisma/fetch-engine": "5.11.0", + "@prisma/get-platform": "5.11.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.11.0", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.11.0", + "@prisma/engines-version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", + "@prisma/get-platform": "5.11.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.11.0", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.11.0" + } + }, + "node_modules/@scure/base": { + "version": "1.1.5", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.2", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.2.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "node_modules/@slack/logger": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", + "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", + "dependencies": { + "@types/node": ">=18.0.0" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.11.0.tgz", + "integrity": "sha512-UlIrDWvuLaDly3QZhCPnwUSI/KYmV1N9LyhuH6EDKCRS1HWZhyTG3Ja46T3D0rYfqdltKYFXbJSSRPwZpwO0cQ==", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.0.4.tgz", + "integrity": "sha512-21tbte7N8itwjG7nsiQbDmXP9T/oqEILuvyL2UtgaZxfSY4a1JWWsLGL5n/hcgS2WE2oxmEHsBuhuRkZDwDovw==", + "dependencies": { + "@slack/logger": "^4.0.0", + "@slack/types": "^2.9.0", + "@types/node": ">=18.0.0", + "@types/retry": "0.12.0", + "axios": "^1.6.5", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.0", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie-parser": { + "version": "1.4.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-QjCOmf5kYwekcsfEKhcEHMK8/SvgnneuSDXFERBuC/DPca2KJIO/gpChTsVb35BoWLBpEAJWz1GFVEArSdtKtw==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/morgan": { + "version": "1.9.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.12.2", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.14", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/route-cache": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@types/route-cache/-/route-cache-0.5.5.tgz", + "integrity": "sha512-HJYRyjX10iV8u9mhl1oiDX5SLm8ldJuFeRWyi0FoeOoX7aVa3L7XHLVm+p+D5i9PCnNElIbbjU6tfM4+eHshyA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/lru-cache": "^4.1.3", + "@types/node": "*", + "ioredis": "^5.3.2" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "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==", + "dev": true + }, + "node_modules/abitype": { + "version": "1.0.0", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "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.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/http-errors/node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/body-parser/node_modules/http-errors/node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/body-parser/node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/character-parser": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "license": "MIT", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/eigen-explorer-backend": { + "resolved": "packages/api", + "link": true + }, + "node_modules/eigen-explorer-backend-db": { + "resolved": "packages/prisma", + "link": true + }, + "node_modules/eigen-explorer-backend-monitor": { + "resolved": "packages/monitor", + "link": true + }, + "node_modules/eigen-explorer-backend-seeder": { + "resolved": "packages/seeder", + "link": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/express": { + "version": "4.19.2", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/http-errors/node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.3", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "7.1.0", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/http-errors": { + "version": "1.6.3", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/setprototypeof": { + "version": "1.1.0", + "license": "ISC" + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "1.4.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.3", + "license": "ISC" + }, + "node_modules/ioredis": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "dev": true, + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ioredis/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ioredis/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isows": { + "version": "1.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jade": { + "version": "0.29.0", + "dependencies": { + "character-parser": "~1.0.0", + "commander": "0.6.1", + "mkdirp": "0.3.x", + "monocle": "~0.1.43", + "transformers": "~1.8.0" + }, + "bin": { + "jade": "bin/jade" + } + }, + "node_modules/jade/node_modules/commander": { + "version": "0.6.1", + "engines": { + "node": ">= 0.4.x" + } + }, + "node_modules/joi": { + "version": "17.12.2", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "dev": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp": { + "version": "0.3.5", + "license": "MIT" + }, + "node_modules/monocle": { + "version": "0.1.48", + "license": "BSD", + "dependencies": { + "readdirp": "~0.2.3" + } + }, + "node_modules/monocle/node_modules/readdirp": { + "version": "0.2.5", + "license": "MIT", + "dependencies": { + "minimatch": ">=0.2.4" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/morgan": { + "version": "1.9.1", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", + "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openapi": { + "resolved": "packages/openapi", + "link": true + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prisma": { + "version": "5.11.0", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.11.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/promise": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "is-promise": "~1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/qs": { + "version": "6.11.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors/node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/raw-body/node_modules/http-errors/node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/raw-body/node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dev": true, + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/route-cache": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/route-cache/-/route-cache-0.7.0.tgz", + "integrity": "sha512-M4LbH7fTVwH72+CDSv1eaeC+0JWjo427Zp7nmn4LXf31OaaRhvK2xMNTHZgVT8caLDXrzREPHW2QPzKN/ZMmEg==", + "dependencies": { + "debug": "3.1.0", + "lru-cache": "4.0.1" + } + }, + "node_modules/route-cache/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/route-cache/node_modules/lru-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz", + "integrity": "sha512-MX0ZnRoVTWXBiNe9dysqKXjvhmQgHsOirh/2rerIVJ8sbQeMxc5OPj0HDpVV3bYjdE6GTHrPf8BEHJqWHFkjHA==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/route-cache/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/http-errors/node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/send/node_modules/http-errors/node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "dev": true + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/transformers": { + "version": "1.8.3", + "license": "MIT", + "dependencies": { + "promise": "~2.0" + } + }, + "node_modules/tsx": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.3.tgz", + "integrity": "sha512-+fQnMqIp/jxZEXLcj6WzYy9FhcS5/Dfk8y4AtzJ6ejKcKqmfTF8Gso/jtrzDggCF2zTU20gJa6n8XqPYwDAUYQ==", + "dev": true, + "dependencies": { + "esbuild": "~0.19.10", + "get-tsconfig": "^4.7.2" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/viem": { + "version": "2.9.21", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.9.21.tgz", + "integrity": "sha512-8GtxPjPGpiN5cmr19zSX9mb1LX/eON3MPxxAd3QmyUFn69Rp566zlREOqE7zM35y5yX59fXwnz6O3X7e9+C9zg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.0", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@scure/bip32": "1.3.2", + "@scure/bip39": "1.2.1", + "abitype": "1.0.0", + "isows": "1.0.3", + "ws": "8.13.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.13.0", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/zod": { + "version": "3.23.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.4.tgz", + "integrity": "sha512-/AtWOKbBgjzEYYQRNfoGKHObgfAZag6qUJX1VbHo2PRBgS+wfWagEY2mizjfyAPcGesrJOcx/wcl0L9WnVrHFw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-error": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/zod-error/-/zod-error-1.5.0.tgz", + "integrity": "sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==", + "dependencies": { + "zod": "^3.20.2" + } + }, + "node_modules/zod-openapi": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/zod-openapi/-/zod-openapi-2.17.0.tgz", + "integrity": "sha512-S6x14LmPWZ3Sbnx71pqzIB6oIox1ce6vHnJY7ZUYLBm8TTEQ6/g6+kwutna5jplAhzphkMbYPbtW3cmHXFcRBg==", + "engines": { + "node": ">=16.11" + }, + "peerDependencies": { + "zod": "^3.21.4" + } + }, + "packages/api": { + "name": "eigen-explorer-backend", + "version": "0.0.1", + "dependencies": { + "@prisma/client": "^5.11.0", + "cookie-parser": "~1.4.4", + "cors": "^2.8.5", + "debug": "~2.6.9", + "dotenv": "^16.4.5", + "express": "^4.18.3", + "helmet": "^7.1.0", + "http-errors": "~1.6.3", + "jade": "^0.29.0", + "joi": "^17.12.2", + "jsonwebtoken": "^9.0.2", + "morgan": "~1.9.1", + "route-cache": "^0.7.0", + "viem": "^2.8.14", + "zod": "^3.23.4", + "zod-error": "^1.5.0", + "zod-openapi": "^2.17.0" + }, + "devDependencies": { + "@types/cookie-parser": "^1.4.7", + "@types/cors": "^2.8.17", + "@types/debug": "^4.1.12", + "@types/express": "^4.17.21", + "@types/morgan": "^1.9.9", + "@types/node": "^20.12.2", + "@types/route-cache": "^0.5.5", + "prisma": "^5.11.0", + "tsx": "^4.7.1", + "typescript": "^5.4.3" + } + }, + "packages/docs": { + "name": "eigen-explorer-backend-docs", + "version": "0.0.1", + "extraneous": true, + "devDependencies": { + "@types/node": "^20.12.2", + "@types/swagger-jsdoc": "^6.0.4", + "swagger-jsdoc": "^6.2.8", + "tsx": "^4.7.1", + "typescript": "^5.4.3" + } + }, + "packages/monitor": { + "name": "eigen-explorer-backend-monitor", + "version": "0.0.1", + "dependencies": { + "@slack/web-api": "^7.0.4", + "dotenv": "^16.4.5", + "viem": "^2.9.21" + }, + "devDependencies": { + "@types/node": "^20.12.2", + "prisma": "^5.11.0", + "tsx": "^4.7.1", + "typescript": "^5.4.5" + } + }, + "packages/monitoring": { + "name": "eigen-explorer-backend-monitoring", + "version": "0.0.1", + "extraneous": true, + "dependencies": { + "@slack/web-api": "^7.0.4", + "dotenv": "^16.4.5", + "viem": "^2.9.21" + }, + "devDependencies": { + "@types/node": "^20.12.2", + "prisma": "^5.11.0", + "tsx": "^4.7.1", + "typescript": "^5.4.5" + } + }, + "packages/openapi": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "zod": "^3.23.4", + "zod-openapi": "^2.17.0" + }, + "devDependencies": { + "nodemon": "^3.1.0", + "tsx": "^4.7.3", + "typescript": "^5.4.5" + } + }, + "packages/openapi-deprecated": { + "name": "eigen-explorer-backend-docs", + "version": "0.0.1", + "extraneous": true, + "dependencies": { + "zod-openapi": "^2.17.0" + }, + "devDependencies": { + "@types/node": "^20.12.2", + "@types/swagger-jsdoc": "^6.0.4", + "swagger-jsdoc": "^6.2.8", + "tsx": "^4.7.3", + "typescript": "^5.4.3" + } + }, + "packages/prisma": { + "name": "eigen-explorer-backend-db", + "version": "0.0.1", + "devDependencies": { + "prisma": "^5.11.0" + } + }, + "packages/seeder": { + "name": "eigen-explorer-backend-seeder", + "version": "0.0.1", + "dependencies": { + "dotenv": "^16.4.5", + "viem": "^2.9.21" + }, + "devDependencies": { + "@types/node": "^20.12.2", + "prisma": "^5.11.0", + "tsx": "^4.7.1", + "typescript": "^5.4.3" + } + } + } } diff --git a/packages/api/.env.example b/packages/api/.env.example index 1b9d3737..f20d3168 100644 --- a/packages/api/.env.example +++ b/packages/api/.env.example @@ -4,4 +4,5 @@ NETWORK_CHAIN_RPC_URL = "" NETWORK_CHAIN_WSS_URL = "" DATABASE_URL = "" DIRECT_URL = "" -CMC_API_KEY = "" \ No newline at end of file +CMC_API_KEY = "" +JWT_SECRET = "" \ No newline at end of file diff --git a/packages/api/package.json b/packages/api/package.json index d8ba759c..31c74b09 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -20,6 +20,7 @@ "http-errors": "~1.6.3", "jade": "^0.29.0", "joi": "^17.12.2", + "jsonwebtoken": "^9.0.2", "morgan": "~1.9.1", "route-cache": "^0.7.0", "viem": "^2.8.14", diff --git a/packages/api/src/routes/avs/avsController.ts b/packages/api/src/routes/avs/avsController.ts index e9e717d4..3c178b1e 100644 --- a/packages/api/src/routes/avs/avsController.ts +++ b/packages/api/src/routes/avs/avsController.ts @@ -81,6 +81,8 @@ export async function getAllAVS(req: Request, res: Response) { return { ...withCuratedMetadata(avs), + createdAtBlock: avs.createdAtBlock.toString(), + updatedAtBlock: avs.updatedAtBlock.toString(), shares, totalOperators, totalStakers, @@ -91,7 +93,9 @@ export async function getAllAVS(req: Request, res: Response) { strategyTokenPrices ) : undefined, - operators: undefined + operators: undefined, + metadataUrl: undefined, + isMetadataSynced: undefined } }) ) @@ -213,6 +217,8 @@ export async function getAVS(req: Request, res: Response) { res.send({ ...withCuratedMetadata(avs), + createdAtBlock: avs.createdAtBlock.toString(), + updatedAtBlock: avs.updatedAtBlock.toString(), shares, totalOperators, totalStakers, @@ -223,7 +229,9 @@ export async function getAVS(req: Request, res: Response) { strategyTokenPrices ) : undefined, - operators: undefined + operators: undefined, + metadataUrl: undefined, + isMetadataSynced: undefined }) } catch (error) { handleAndReturnErrorResponse(req, res, error) @@ -422,6 +430,36 @@ function withOperatorShares(avsOperators) { })) } +/** + * Protected route to invalidate the metadata of a given address + * + * @param req + * @param res + */ +export async function invalidateMetadata(req: Request, res: Response) { + const paramCheck = EthereumAddressSchema.safeParse(req.params.address) + if (!paramCheck.success) { + return handleAndReturnErrorResponse(req, res, paramCheck.error) + } + + try { + const { address } = req.params + + const updateResult = await prisma.avs.updateMany({ + where: { address: address.toLowerCase() }, + data: { isMetadataSynced: false } + }) + + if (updateResult.count === 0) { + throw new Error('Address not found.') + } + + res.send({ message: 'Metadata invalidated successfully.' }) + } catch (error) { + handleAndReturnErrorResponse(req, res, error) + } +} + // Helper functions function withCuratedMetadata(avs): Avs { // Replace metadata with curated metadata diff --git a/packages/api/src/routes/avs/avsRoutes.ts b/packages/api/src/routes/avs/avsRoutes.ts index 76674f16..21d0752b 100644 --- a/packages/api/src/routes/avs/avsRoutes.ts +++ b/packages/api/src/routes/avs/avsRoutes.ts @@ -5,7 +5,9 @@ import { getAVS, getAVSOperators, getAVSStakers, + invalidateMetadata } from './avsController'; +import { authenticateJWT } from '../../utils/jwtUtils' import routeCache from "route-cache"; @@ -22,4 +24,12 @@ router.get('/:address/stakers', routeCache.cacheSeconds(120), getAVSStakers); router.get('/:address/operators', routeCache.cacheSeconds(120), getAVSOperators); +// Protected routes +router.get( + '/:address/invalidate-metadata', + authenticateJWT, + routeCache.cacheSeconds(120), + invalidateMetadata +); + export default router; diff --git a/packages/api/src/routes/deposits/depositController.ts b/packages/api/src/routes/deposits/depositController.ts new file mode 100644 index 00000000..566a6ba7 --- /dev/null +++ b/packages/api/src/routes/deposits/depositController.ts @@ -0,0 +1,71 @@ +import type { Request, Response } from 'express' +import { Prisma } from '@prisma/client' +import prisma from '../../utils/prismaClient' +import { handleAndReturnErrorResponse } from '../../schema/errors' +import { DepositListQuerySchema } from '../../schema/zod/schemas/deposit' +import { PaginationQuerySchema } from '../../schema/zod/schemas/paginationQuery' + +/** + * Route to get a list of all deposits, filtered by stakerAddress, strategyAddress and tokenAddress + * + * @param req + * @param res + */ + +export async function getAllDeposits(req: Request, res: Response) { + // Validate query + const result = DepositListQuerySchema.and(PaginationQuerySchema).safeParse( + req.query + ) + if (!result.success) { + return handleAndReturnErrorResponse(req, res, result.error) + } + + const { stakerAddress, tokenAddress, strategyAddress, skip, take } = + result.data + + try { + const filterQuery: Prisma.DepositWhereInput = {} + + if (stakerAddress) { + filterQuery.stakerAddress = stakerAddress.toLowerCase() + } + + if (tokenAddress) { + filterQuery.tokenAddress = tokenAddress.toLowerCase() + } + + if (strategyAddress) { + filterQuery.strategyAddress = strategyAddress.toLowerCase() + } + + const depositCount = await prisma.deposit.count({ + where: filterQuery + }) + + const depositRecords = await prisma.deposit.findMany({ + where: filterQuery, + skip, + take, + orderBy: { createdAtBlock: 'desc' } + }) + + const data = depositRecords.map((deposit) => { + return { + ...deposit, + createdAtBlock: Number(deposit.createdAtBlock) + } + }) + + res.send({ + data, + meta: { + total: depositCount, + skip, + take + } + }) + } catch (error) { + handleAndReturnErrorResponse(req, res, error) + } +} diff --git a/packages/api/src/routes/deposits/depositRoutes.ts b/packages/api/src/routes/deposits/depositRoutes.ts new file mode 100644 index 00000000..e05cd0e2 --- /dev/null +++ b/packages/api/src/routes/deposits/depositRoutes.ts @@ -0,0 +1,8 @@ +import express from 'express' +import { getAllDeposits } from './depositController' + +const router = express.Router() + +router.get('/', getAllDeposits) + +export default router diff --git a/packages/api/src/routes/index.ts b/packages/api/src/routes/index.ts index dab3ccc3..6550214c 100644 --- a/packages/api/src/routes/index.ts +++ b/packages/api/src/routes/index.ts @@ -5,6 +5,7 @@ import operatorRoutes from './operators/operatorRoutes' import stakerRoutes from './stakers/stakerRoutes' import metricRoutes from './metrics/metricRoutes' import withdrawalRoutes from './withdrawals/withdrawalRoutes' +import depositRoutes from './deposits/depositRoutes' const apiRouter = express.Router() @@ -23,5 +24,6 @@ apiRouter.use('/operators', operatorRoutes) apiRouter.use('/stakers', stakerRoutes) apiRouter.use('/metrics', metricRoutes) apiRouter.use('/withdrawals', withdrawalRoutes) +apiRouter.use('/deposits', depositRoutes) export default apiRouter diff --git a/packages/api/src/routes/metrics/metricController.ts b/packages/api/src/routes/metrics/metricController.ts index 6947bda6..2595a210 100644 --- a/packages/api/src/routes/metrics/metricController.ts +++ b/packages/api/src/routes/metrics/metricController.ts @@ -11,6 +11,7 @@ import { handleAndReturnErrorResponse } from '../../schema/errors' import { getAvsFilterQuery } from '../avs/avsController' import { fetchStrategyTokenPrices } from '../../utils/tokenPrices' import { getStrategiesWithShareUnderlying } from '../strategies/strategiesController' +import { HistoricalCountSchema } from '../../schema/zod/schemas/historicalCountQuery' /** * Route to get explorer metrics @@ -140,6 +141,69 @@ export async function getTotalStakers(req: Request, res: Response) { } } +export async function getHistoricalAvsCount(req: Request, res: Response) { + const paramCheck = HistoricalCountSchema.safeParse(req.query) + if (!paramCheck.success) { + return handleAndReturnErrorResponse(req, res, paramCheck.error) + } + + try { + const { frequency, variant, startAt, endAt } = paramCheck.data + const data = await doGetHistoricalCount( + 'avs', + startAt, + endAt, + frequency, + variant + ) + res.status(200).send({ data }) + } catch (error) { + handleAndReturnErrorResponse(req, res, error) + } +} + +export async function getHistoricalOperatorCount(req: Request, res: Response) { + const paramCheck = HistoricalCountSchema.safeParse(req.query) + if (!paramCheck.success) { + return handleAndReturnErrorResponse(req, res, paramCheck.error) + } + + try { + const { frequency, variant, startAt, endAt } = paramCheck.data + const data = await doGetHistoricalCount( + 'operator', + startAt, + endAt, + frequency, + variant + ) + res.status(200).send({ data }) + } catch (error) { + handleAndReturnErrorResponse(req, res, error) + } +} + +export async function getHistoricalStakerCount(req: Request, res: Response) { + const paramCheck = HistoricalCountSchema.safeParse(req.query) + if (!paramCheck.success) { + return handleAndReturnErrorResponse(req, res, paramCheck.error) + } + + try { + const { frequency, variant, startAt, endAt } = paramCheck.data + const data = await doGetHistoricalCount( + 'staker', + startAt, + endAt, + frequency, + variant + ) + res.status(200).send({ data }) + } catch (error) { + handleAndReturnErrorResponse(req, res, error) + } +} + // ================================================ async function doGetTvl() { @@ -255,3 +319,84 @@ async function doGetTotalStakerCount() { return stakers } + +async function doGetHistoricalCount( + modelName: 'avs' | 'operator' | 'staker', + startAt: string, + endAt: string, + frequency: string, + variant: string +) { + function resetTime(date: Date) { + date.setUTCMinutes(0, 0, 0) + return date + } + + const startDate = resetTime(new Date(startAt)) + const endDate = resetTime(new Date(endAt)) + + if (!['avs', 'operator', 'staker'].includes(modelName)) { + throw new Error('Invalid model name') + } + + // biome-ignore lint/suspicious/noExplicitAny: + const model = prisma[modelName] as any + + const initialTally = await model.count({ + where: { + createdAt: { + lt: startDate + } + } + }) + + const modelData = await model.findMany({ + where: { + createdAt: { + gte: startDate, + lte: endDate + } + }, + orderBy: { + createdAt: 'asc' + } + }) + + let tally = initialTally + const results: { ts: string; value: number }[] = [] + let currentDate = startDate.getTime() + + const timeInterval = + { + '1h': 3600000, + '1d': 86400000, + '7d': 604800000 + }[frequency] || 3600000 + + while (currentDate <= endDate.getTime()) { + const nextDate = new Date(currentDate + timeInterval).getTime() + + const intervalData = modelData.filter( + (data) => + data.createdAt.getTime() >= currentDate && + data.createdAt.getTime() < nextDate + ) + + if (variant === 'count') { + results.push({ + ts: new Date(Number(currentDate)).toISOString(), + value: intervalData.length + }) + } else { + tally += intervalData.length + results.push({ + ts: new Date(Number(currentDate)).toISOString(), + value: tally + }) + } + + currentDate = nextDate + } + + return results +} diff --git a/packages/api/src/routes/metrics/metricRoutes.ts b/packages/api/src/routes/metrics/metricRoutes.ts index 5ed819fa..430b82d5 100644 --- a/packages/api/src/routes/metrics/metricRoutes.ts +++ b/packages/api/src/routes/metrics/metricRoutes.ts @@ -1,35 +1,60 @@ -import express from 'express'; +import express from 'express' import { - getMetrics, - getTotalAvs, - getTotalOperators, - getTotalStakers, - getTvl, - getTvlBeaconChain, - getTvlRestaking, - getTvlRestakingByStrategy, -} from './metricController'; + getMetrics, + getTotalAvs, + getTotalOperators, + getTotalStakers, + getTvl, + getTvlBeaconChain, + getTvlRestaking, + getTvlRestakingByStrategy, + getHistoricalAvsCount, + getHistoricalOperatorCount, + getHistoricalStakerCount +} from './metricController' + +import routeCache from 'route-cache' + +const router = express.Router() -import routeCache from "route-cache"; +// API routes for /metrics -const router = express.Router(); +router.get('/', routeCache.cacheSeconds(120), getMetrics) -// API routes for /metrics +router.get('/tvl', routeCache.cacheSeconds(120), getTvl) + +router.get('/tvl/beacon-chain', routeCache.cacheSeconds(120), getTvlBeaconChain) -router.get('/', routeCache.cacheSeconds(120), getMetrics); +router.get('/tvl/restaking', routeCache.cacheSeconds(120), getTvlRestaking) -router.get('/tvl', routeCache.cacheSeconds(120), getTvl); +router.get( + '/tvl/restaking/:strategy', + routeCache.cacheSeconds(120), + getTvlRestakingByStrategy +) -router.get('/tvl/beacon-chain', routeCache.cacheSeconds(120), getTvlBeaconChain); +router.get('/total-avs', routeCache.cacheSeconds(120), getTotalAvs) -router.get('/tvl/restaking', routeCache.cacheSeconds(120), getTvlRestaking); +router.get('/total-operators', routeCache.cacheSeconds(120), getTotalOperators) -router.get('/tvl/restaking/:strategy', routeCache.cacheSeconds(120), getTvlRestakingByStrategy); +router.get('/total-stakers', routeCache.cacheSeconds(120), getTotalStakers) -router.get('/total-avs', routeCache.cacheSeconds(120), getTotalAvs); +router.get( + '/historical/avs', + routeCache.cacheSeconds(120), + getHistoricalAvsCount +) -router.get('/total-operators', routeCache.cacheSeconds(120), getTotalOperators); +router.get( + '/historical/operators', + routeCache.cacheSeconds(120), + getHistoricalOperatorCount +) -router.get('/total-stakers', routeCache.cacheSeconds(120), getTotalStakers); +router.get( + '/historical/stakers', + routeCache.cacheSeconds(120), + getHistoricalStakerCount +) -export default router; +export default router diff --git a/packages/api/src/routes/operators/operatorController.ts b/packages/api/src/routes/operators/operatorController.ts index 2a961ba3..8f4145ca 100644 --- a/packages/api/src/routes/operators/operatorController.ts +++ b/packages/api/src/routes/operators/operatorController.ts @@ -1,6 +1,7 @@ import type { Request, Response } from 'express' import prisma from '../../utils/prismaClient' import { PaginationQuerySchema } from '../../schema/zod/schemas/paginationQuery' +import { EthereumAddressSchema } from '../../schema/zod/schemas/base/ethereumAddress' import { WithTvlQuerySchema } from '../../schema/zod/schemas/withTvlQuery' import { handleAndReturnErrorResponse } from '../../schema/errors' import { @@ -46,6 +47,8 @@ export async function getAllOperators(req: Request, res: Response) { const operators = operatorRecords.map((operator) => ({ ...operator, + createdAtBlock: operator.createdAtBlock.toString(), + updatedAtBlock: operator.updatedAtBlock.toString(), totalStakers: operator.stakers.length, tvl: withTvl ? sharesToTVL( @@ -54,7 +57,9 @@ export async function getAllOperators(req: Request, res: Response) { strategyTokenPrices ) : undefined, - stakers: undefined + stakers: undefined, + metadataUrl: undefined, + isMetadataSynced: undefined })) res.send({ @@ -87,7 +92,7 @@ export async function getOperator(req: Request, res: Response) { try { const { address } = req.params - const operator = await prisma.operator.findUniqueOrThrow({ + const operator = await prisma.operator.findUniqueOrThrow({ where: { address: address.toLowerCase() }, include: { shares: { @@ -102,8 +107,11 @@ export async function getOperator(req: Request, res: Response) { ? await getStrategiesWithShareUnderlying() : [] + res.send({ ...operator, + createdAtBlock: operator.createdAtBlock.toString(), + updatedAtBlock: operator.updatedAtBlock.toString(), totalStakers: operator.stakers.length, tvl: withTvl ? sharesToTVL( @@ -112,8 +120,40 @@ export async function getOperator(req: Request, res: Response) { strategyTokenPrices ) : undefined, - stakers: undefined + stakers: undefined, + metadataUrl: undefined, + isMetadataSynced: undefined + }) + } catch (error) { + handleAndReturnErrorResponse(req, res, error) + } +} + +/** + * Protected route to invalidate the metadata of a given address + * + * @param req + * @param res + */ +export async function invalidateMetadata(req: Request, res: Response) { + const paramCheck = EthereumAddressSchema.safeParse(req.params.address) + if (!paramCheck.success) { + return handleAndReturnErrorResponse(req, res, paramCheck.error) + } + + try { + const { address } = req.params + + const updateResult = await prisma.operator.updateMany({ + where: { address: address.toLowerCase() }, + data: { isMetadataSynced: false } }) + + if (updateResult.count === 0) { + throw new Error('Address not found.') + } + + res.send({ message: 'Metadata invalidated successfully.' }) } catch (error) { handleAndReturnErrorResponse(req, res, error) } diff --git a/packages/api/src/routes/operators/operatorRoutes.ts b/packages/api/src/routes/operators/operatorRoutes.ts index bf5e71ac..c52846ff 100644 --- a/packages/api/src/routes/operators/operatorRoutes.ts +++ b/packages/api/src/routes/operators/operatorRoutes.ts @@ -1,5 +1,6 @@ import express from 'express' -import { getAllOperators, getOperator } from './operatorController' +import { getAllOperators, getOperator, invalidateMetadata } from './operatorController' +import { authenticateJWT } from '../../utils/jwtUtils' import routeCache from "route-cache"; @@ -97,4 +98,12 @@ router.get('/', routeCache.cacheSeconds(120), getAllOperators) */ router.get('/:address', routeCache.cacheSeconds(120), getOperator) +// Protected routes +router.get( + '/:address/invalidate-metadata', + authenticateJWT, + routeCache.cacheSeconds(120), + invalidateMetadata +) + export default router diff --git a/packages/api/src/routes/stakers/stakerController.ts b/packages/api/src/routes/stakers/stakerController.ts index ffb646b5..c17dcadb 100644 --- a/packages/api/src/routes/stakers/stakerController.ts +++ b/packages/api/src/routes/stakers/stakerController.ts @@ -340,3 +340,46 @@ export async function getStakerWithdrawalsCompleted( handleAndReturnErrorResponse(req, res, error) } } + +export async function getStakerDeposits(req: Request, res: Response) { + // Validate query + const result = PaginationQuerySchema.safeParse(req.query) + if (!result.success) { + return handleAndReturnErrorResponse(req, res, result.error) + } + + const { skip, take } = result.data + + try { + const { address } = req.params + const filterQuery = { stakerAddress: address } + + const depositCount = await prisma.deposit.count({ + where: filterQuery + }) + const depositRecords = await prisma.deposit.findMany({ + where: filterQuery, + skip, + take, + orderBy: { createdAtBlock: 'desc' } + }) + + const data = depositRecords.map((deposit) => { + return { + ...deposit, + createdAtBlock: Number(deposit.createdAtBlock) + } + }) + + res.send({ + data, + meta: { + total: depositCount, + skip, + take + } + }) + } catch (error) { + handleAndReturnErrorResponse(req, res, error) + } +} diff --git a/packages/api/src/routes/stakers/stakerRoutes.ts b/packages/api/src/routes/stakers/stakerRoutes.ts index 36becb59..070f788e 100644 --- a/packages/api/src/routes/stakers/stakerRoutes.ts +++ b/packages/api/src/routes/stakers/stakerRoutes.ts @@ -5,7 +5,8 @@ import { getStakerWithdrawals, getStakerWithdrawalsCompleted, getStakerWithdrawalsQueued, - getStakerWithdrawalsWithdrawable + getStakerWithdrawalsWithdrawable, + getStakerDeposits } from './stakerController' const router = express.Router() @@ -22,4 +23,6 @@ router.get( ) router.get('/:address/withdrawals/completed', getStakerWithdrawalsCompleted) +router.get('/:address/deposits', getStakerDeposits) + export default router diff --git a/packages/api/src/schema/zod/schemas/deposit.ts b/packages/api/src/schema/zod/schemas/deposit.ts new file mode 100644 index 00000000..b9616a93 --- /dev/null +++ b/packages/api/src/schema/zod/schemas/deposit.ts @@ -0,0 +1,22 @@ +import z from '../' + +export const DepositListQuerySchema = z.object({ + stakerAddress: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') + .optional() + .describe('The address of the staker') + .openapi({ example: '0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd' }), + tokenAddress: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') + .optional() + .describe('The address of the token deposited') + .openapi({ example: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb' }), + strategyAddress: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') + .optional() + .describe('The contract address of the restaking strategy') + .openapi({ example: '0x0fe4f44bee93503346a3ac9ee5a26b130a5796d6' }) +}) diff --git a/packages/api/src/schema/zod/schemas/historicalCountQuery.ts b/packages/api/src/schema/zod/schemas/historicalCountQuery.ts new file mode 100644 index 00000000..ffed55de --- /dev/null +++ b/packages/api/src/schema/zod/schemas/historicalCountQuery.ts @@ -0,0 +1,116 @@ +import z from '..' + +export const HistoricalCountSchema = z + .object({ + frequency: z + .enum(['1h', '1d', '7d']) + .default('1h') + .describe('Frequency of data points'), + variant: z + .enum(['count', 'cumulative']) + .default('cumulative') + .describe('Type of tally, count or cumulative'), + startAt: z + .string() + .optional() + .describe('Start date in ISO string format') + .refine((val) => !val || !Number.isNaN(Date.parse(val)), { + message: 'Invalid date format' + }) + .default('') + .describe('Start date in ISO string format'), + endAt: z + .string() + .optional() + .describe('End date in ISO string format') + .refine((val) => !val || !Number.isNaN(Date.parse(val)), { + message: 'Invalid date format' + }) + .default('') + .describe('End date in ISO string format') + }) + .refine( + (data) => { + const { startAt, endAt, frequency } = data + if (!endAt) { + if (startAt) { + const startDate = new Date(startAt) + + if (frequency === '1h') { + const endDate = new Date(startDate.getTime() + 48 * 60 * 60 * 1000) + data.endAt = endDate.toISOString() + } + if (frequency === '1d') { + const endDate = new Date( + startDate.getTime() + 31 * 24 * 60 * 60 * 1000 + ) + data.endAt = endDate.toISOString() + } + if (frequency === '7d') { + const endDate = new Date( + startDate.getTime() + 365 * 24 * 60 * 60 * 1000 + ) + data.endAt = endDate.toISOString() + } + } else { + const endDate = new Date() + data.endAt = endDate.toISOString() + } + } + + if (!startAt) { + const endDate = new Date(data.endAt) + + if (frequency === '1h') { + const startDate = new Date(endDate.getTime() - 48 * 60 * 60 * 1000) + data.startAt = startDate.toISOString() + } + if (frequency === '1d') { + const startDate = new Date( + endDate.getTime() - 31 * 24 * 60 * 60 * 1000 + ) + data.startAt = startDate.toISOString() + } + if (frequency === '7d') { + const startDate = new Date( + endDate.getTime() - 365 * 24 * 60 * 60 * 1000 + ) + data.startAt = startDate.toISOString() + } + } + + const start = new Date(data.startAt) + const end = new Date(data.endAt) + return end.getTime() >= start.getTime() + }, + { + message: 'endAt must be after startAt', + path: ['endAt'] + } + ) + .refine( + (data) => { + const { frequency, startAt, endAt } = data + const start = new Date(startAt) + const end = new Date(endAt) + const durationMs = end.getTime() - start.getTime() + + if (frequency === '1h' && durationMs > 48 * 60 * 60 * 1000) { + return false + } + if (frequency === '1d' && durationMs > 31 * 24 * 60 * 60 * 1000) { + return false + } + if (frequency === '7d' && durationMs > 365 * 24 * 60 * 60 * 1000) { + return false + } + return true + }, + { + message: + 'Duration between startAt and endAt exceeds the allowed maximum for the selected frequency', + path: ['startAt', 'endAt'] + } + ) + +export default HistoricalCountSchema diff --git a/packages/api/src/schema/zod/schemas/withdrawal.ts b/packages/api/src/schema/zod/schemas/withdrawal.ts index 1d91c029..54b47ff5 100644 --- a/packages/api/src/schema/zod/schemas/withdrawal.ts +++ b/packages/api/src/schema/zod/schemas/withdrawal.ts @@ -1,20 +1,27 @@ -import z from '../' +import z from '../'; export const WithdrawalListQuerySchema = z.object({ - stakerAddress: z - .string() - .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') - .optional(), - - delegatedTo: z - .string() - .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') - .optional(), - - strategyAddress: z - .string() - .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') - .optional(), - - status: z.enum(['queued', 'queued_withdrawable', 'completed']).optional() -}) + stakerAddress: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') + .optional() + .describe('The address of the staker') + .openapi({ example: '0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd' }), + delegatedTo: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') + .optional() + .describe('The address of the operator to which the stake is delegated') + .openapi({ example: '0x5accc90436492f24e6af278569691e2c942a676d' }), + strategyAddress: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address') + .optional() + .describe('The contract address of the restaking strategy') + .openapi({ example: '0x0fe4f44bee93503346a3ac9ee5a26b130a5796d6' }), + status: z + .enum(['queued', 'queued_withdrawable', 'completed']) + .optional() + .describe('The status of the withdrawal') + .openapi({ example: 'queued' }), +}); diff --git a/packages/api/src/utils/jwtUtils.ts b/packages/api/src/utils/jwtUtils.ts new file mode 100644 index 00000000..24bcf471 --- /dev/null +++ b/packages/api/src/utils/jwtUtils.ts @@ -0,0 +1,26 @@ +import 'dotenv/config' +import jwt from 'jsonwebtoken' +import type { Request, Response, NextFunction } from 'express' + +const JWT_SECRET = process.env.JWT_SECRET || '' + +export function authenticateJWT( + req: Request, + res: Response, + next: NextFunction +) { + const token = req.header('Authorization')?.split(' ')[1] + + if (!token) { + return res + .status(401) + .json({ message: 'Access denied. No token provided.' }) + } + + try { + jwt.verify(token, JWT_SECRET) + next() + } catch (error) { + res.status(400).json({ message: 'Invalid token.' }) + } +} diff --git a/packages/openapi/openapi.json b/packages/openapi/openapi.json index 83e8b590..6bd516af 100644 --- a/packages/openapi/openapi.json +++ b/packages/openapi/openapi.json @@ -16,6 +16,70 @@ } ], "paths": { + "/version": { + "get": { + "operationId": "getVersion", + "summary": "Retrieve API server version", + "description": "Returns API server version.", + "tags": [ + "System" + ], + "responses": { + "200": { + "description": "The API server version.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "The version of the API server", + "example": "v0.0.1" + } + }, + "required": [ + "version" + ] + } + } + } + } + } + } + }, + "/health": { + "get": { + "operationId": "getHealth", + "summary": "Retrieve API server status", + "description": "Returns API server status.", + "tags": [ + "System" + ], + "responses": { + "200": { + "description": "The API server status.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "The status of the API server", + "example": "ok" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + } + }, "/metrics": { "get": { "operationId": "getAllMetrics", @@ -55,6 +119,19 @@ "stETH": 2000000 } }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The total value locked (TVL) in each restaking strategy, denominated in ETH", + "example": { + "cbETH": 1000000, + "stETH": 2000000 + } + }, "tvlBeaconChain": { "type": "number", "description": "The total value locked (TVL) in ETH in the beacon chain ETH strategy", @@ -80,6 +157,7 @@ "tvl", "tvlRestaking", "tvlStrategies", + "tvlStrategiesEth", "tvlBeaconChain", "totalAVS", "totalOperators", @@ -252,11 +330,25 @@ "cbETH": 1000000, "stETH": 2000000 } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The total value locked (TVL) in each restaking strategy, denominated in ETH", + "example": { + "cbETH": 1000000, + "stETH": 2000000 + } } }, "required": [ "tvl", - "tvlStrategies" + "tvlStrategies", + "tvlStrategiesEth" ] } } @@ -520,6 +612,21 @@ "AVS" ], "parameters": [ + { + "in": "query", + "name": "withTvl", + "description": "Toggle whether the route should calculate the TVL from shares", + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ], + "default": "false", + "description": "Toggle whether the route should calculate the TVL from shares", + "example": "false" + } + }, { "in": "query", "name": "skip", @@ -541,21 +648,6 @@ "description": "The number of records to return for pagination", "example": 12 } - }, - { - "in": "query", - "name": "withTvl", - "description": "Toggle whether the route should calculate the TVL from shares", - "schema": { - "type": "string", - "enum": [ - "true", - "false" - ], - "default": "false", - "description": "Toggle whether the route should calculate the TVL from shares", - "example": "false" - } } ], "responses": { @@ -712,6 +804,19 @@ "Eigen": 1000000, "cbETH": 2000000 } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in ETH", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } } }, "required": [ @@ -719,7 +824,8 @@ "tvlBeaconChain", "tvlRestaking", "tvlWETH", - "tvlStrategies" + "tvlStrategies", + "tvlStrategiesEth" ], "description": "The total value locked (TVL) in the AVS", "example": { @@ -730,6 +836,10 @@ "tvlStrategies": { "Eigen": 1000000, "cbETH": 2000000 + }, + "tvlStrategiesEth": { + "stETH": 1000000, + "cbETH": 2000000 } } } @@ -1111,6 +1221,19 @@ "Eigen": 1000000, "cbETH": 2000000 } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in ETH", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } } }, "required": [ @@ -1118,7 +1241,8 @@ "tvlBeaconChain", "tvlRestaking", "tvlWETH", - "tvlStrategies" + "tvlStrategies", + "tvlStrategiesEth" ], "description": "The total value locked (TVL) in the AVS", "example": { @@ -1129,6 +1253,10 @@ "tvlStrategies": { "Eigen": 1000000, "cbETH": 2000000 + }, + "tvlStrategiesEth": { + "stETH": 1000000, + "cbETH": 2000000 } } } @@ -1192,6 +1320,21 @@ }, "required": true }, + { + "in": "query", + "name": "withTvl", + "description": "Toggle whether the route should calculate the TVL from shares", + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ], + "default": "false", + "description": "Toggle whether the route should calculate the TVL from shares", + "example": "false" + } + }, { "in": "query", "name": "skip", @@ -1271,16 +1414,84 @@ } }, "tvl": { - "type": "number", - "description": "The total value locked in the AVS staker", - "example": 0.040888428658906045 + "type": "object", + "properties": { + "tvl": { + "type": "number", + "description": "The combined TVL of all restaking strategies in ETH", + "example": 1000000 + }, + "tvlBeaconChain": { + "type": "number", + "description": "The TVL of Beacon Chain restaking strategy in ETH", + "example": 1000000 + }, + "tvlRestaking": { + "type": "number", + "description": "The combined TVL of all liquid restaking strategies in ETH", + "example": 1000000 + }, + "tvlWETH": { + "type": "number", + "description": "The TVL of WETH restaking strategy in ETH", + "example": 1000000 + }, + "tvlStrategies": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in the strategy's native token", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in its native token", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in ETH", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } + } + }, + "required": [ + "tvl", + "tvlBeaconChain", + "tvlRestaking", + "tvlWETH", + "tvlStrategies", + "tvlStrategiesEth" + ], + "description": "The total value locked (TVL) in the AVS staker", + "example": { + "tvl": 1000000, + "tvlBeaconChain": 1000000, + "tvlWETH": 1000000, + "tvlRestaking": 1000000, + "tvlStrategies": { + "Eigen": 1000000, + "cbETH": 2000000 + }, + "tvlStrategiesEth": { + "stETH": 1000000, + "cbETH": 2000000 + } + } } }, "required": [ "address", "operatorAddress", - "shares", - "tvl" + "shares" ] } }, @@ -1360,6 +1571,21 @@ }, "required": true }, + { + "in": "query", + "name": "withTvl", + "description": "Toggle whether the route should calculate the TVL from shares", + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ], + "default": "false", + "description": "Toggle whether the route should calculate the TVL from shares", + "example": "false" + } + }, { "in": "query", "name": "skip", @@ -1381,21 +1607,6 @@ "description": "The number of records to return for pagination", "example": 12 } - }, - { - "in": "query", - "name": "withTvl", - "description": "Toggle whether the route should calculate the TVL from shares", - "schema": { - "type": "string", - "enum": [ - "true", - "false" - ], - "default": "false", - "description": "Toggle whether the route should calculate the TVL from shares", - "example": "false" - } } ], "responses": { @@ -1536,6 +1747,19 @@ "Eigen": 1000000, "cbETH": 2000000 } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in ETH", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } } }, "required": [ @@ -1543,7 +1767,8 @@ "tvlBeaconChain", "tvlRestaking", "tvlWETH", - "tvlStrategies" + "tvlStrategies", + "tvlStrategiesEth" ], "description": "The total value locked (TVL) in the AVS operator", "example": { @@ -1554,6 +1779,10 @@ "tvlStrategies": { "Eigen": 1000000, "cbETH": 2000000 + }, + "tvlStrategiesEth": { + "stETH": 1000000, + "cbETH": 2000000 } } } @@ -1641,45 +1870,45 @@ "parameters": [ { "in": "query", - "name": "skip", - "description": "The number of records to skip for pagination", + "name": "withTvl", + "description": "Toggle whether the route should calculate the TVL from shares", "schema": { "type": "string", - "default": "0", - "description": "The number of records to skip for pagination", - "example": 0 - } - }, - { - "in": "query", - "name": "take", - "description": "The number of records to return for pagination", + "enum": [ + "true", + "false" + ], + "default": "false", + "description": "Toggle whether the route should calculate the TVL from shares", + "example": "false" + } + }, + { + "in": "query", + "name": "skip", + "description": "The number of records to skip for pagination", "schema": { "type": "string", - "default": "12", - "description": "The number of records to return for pagination", - "example": 12 + "default": "0", + "description": "The number of records to skip for pagination", + "example": 0 } }, { "in": "query", - "name": "withTvl", - "description": "Toggle whether the route should calculate the TVL from shares", + "name": "take", + "description": "The number of records to return for pagination", "schema": { "type": "string", - "enum": [ - "true", - "false" - ], - "default": "false", - "description": "Toggle whether the route should calculate the TVL from shares", - "example": "false" + "default": "12", + "description": "The number of records to return for pagination", + "example": 12 } } ], "responses": { "200": { - "description": "The list of Operators records.", + "description": "The list of operator records.", "content": { "application/json": { "schema": { @@ -1815,6 +2044,19 @@ "Eigen": 1000000, "cbETH": 2000000 } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in ETH", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } } }, "required": [ @@ -1822,7 +2064,8 @@ "tvlBeaconChain", "tvlRestaking", "tvlWETH", - "tvlStrategies" + "tvlStrategies", + "tvlStrategiesEth" ], "description": "The total value locked (TVL) in the AVS operator", "example": { @@ -1833,6 +2076,10 @@ "tvlStrategies": { "Eigen": 1000000, "cbETH": 2000000 + }, + "tvlStrategiesEth": { + "stETH": 1000000, + "cbETH": 2000000 } } } @@ -1921,11 +2168,11 @@ { "in": "path", "name": "address", - "description": "The address of the operator.", + "description": "The address of the operator", "schema": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$", - "description": "The address of the operator.", + "description": "The address of the operator", "example": "0x00107cfdeaddc0a3160ed2f6fedd627f313e7b1b" }, "required": true @@ -2079,6 +2326,19 @@ "Eigen": 1000000, "cbETH": 2000000 } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in ETH", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } } }, "required": [ @@ -2086,7 +2346,8 @@ "tvlBeaconChain", "tvlRestaking", "tvlWETH", - "tvlStrategies" + "tvlStrategies", + "tvlStrategiesEth" ], "description": "The total value locked (TVL) in the AVS operator", "example": { @@ -2097,6 +2358,10 @@ "tvlStrategies": { "Eigen": 1000000, "cbETH": 2000000 + }, + "tvlStrategiesEth": { + "stETH": 1000000, + "cbETH": 2000000 } } } @@ -2140,6 +2405,1649 @@ } } } + }, + "/withdrawals": { + "get": { + "operationId": "getAllWithdrawals", + "summary": "Retrieve all withdrawals", + "description": "Returns all withdrawal data, including the withdrawal root, nonce, withdrawal status, and other relevant information.", + "tags": [ + "Withdrawals" + ], + "parameters": [ + { + "in": "query", + "name": "stakerAddress", + "description": "The address of the staker", + "schema": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + } + }, + { + "in": "query", + "name": "delegatedTo", + "description": "The address of the operator to which the stake is delegated", + "schema": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the operator to which the stake is delegated", + "example": "0x5accc90436492f24e6af278569691e2c942a676d" + } + }, + { + "in": "query", + "name": "strategyAddress", + "description": "The contract address of the restaking strategy", + "schema": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0x0fe4f44bee93503346a3ac9ee5a26b130a5796d6" + } + }, + { + "in": "query", + "name": "status", + "description": "The status of the withdrawal", + "schema": { + "type": "string", + "enum": [ + "queued", + "queued_withdrawable", + "completed" + ], + "description": "The status of the withdrawal", + "example": "queued" + } + }, + { + "in": "query", + "name": "skip", + "description": "The number of records to skip for pagination", + "schema": { + "type": "string", + "default": "0", + "description": "The number of records to skip for pagination", + "example": 0 + } + }, + { + "in": "query", + "name": "take", + "description": "The number of records to return for pagination", + "schema": { + "type": "string", + "default": "12", + "description": "The number of records to return for pagination", + "example": 12 + } + } + ], + "responses": { + "200": { + "description": "The list of withdrawals.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "withdrawalRoot": { + "type": "string", + "description": "The root hash of the withdrawal", + "example": "0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31" + }, + "nonce": { + "type": "number", + "description": "The nonce of the withdrawal", + "example": 0 + }, + "isCompleted": { + "type": "boolean", + "description": "Indicates if the withdrawal is completed", + "example": false + }, + "stakerAddress": { + "type": "string", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "delegatedTo": { + "type": "string", + "description": "The operator address to which staking is delegated", + "example": "0x0000000000000000000000000000000000000000" + }, + "withdrawerAddress": { + "type": "string", + "description": "The address of the withdrawer", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "shares": { + "type": "array", + "items": { + "type": "object", + "properties": { + "strategyAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0" + }, + "shares": { + "type": "string", + "description": "The amount of shares held in the strategy", + "example": "1277920000000000000000000" + } + }, + "required": [ + "strategyAddress", + "shares" + ] + }, + "description": "The list of strategy shares", + "example": [ + { + "strategyAddress": "0x93c4b944d05dfe6df7645a86cd2206016c51564d", + "shares": "1000288824523326631" + } + ] + }, + "startBlock": { + "type": "number", + "description": "The block number when the withdrawal was queued", + "example": 19912470 + }, + "createdAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was recorded by EigenExplorer", + "example": 19912470 + }, + "updatedAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was last updated", + "example": 19912470 + } + }, + "required": [ + "withdrawalRoot", + "nonce", + "isCompleted", + "stakerAddress", + "delegatedTo", + "withdrawerAddress", + "shares", + "startBlock", + "createdAtBlock", + "updatedAtBlock" + ] + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "number", + "description": "Total number of records in the database", + "example": 30 + }, + "skip": { + "type": "number", + "description": "The number of skiped records for this query", + "example": 0 + }, + "take": { + "type": "number", + "description": "The number of records returned for this query", + "example": 12 + } + }, + "required": [ + "total", + "skip", + "take" + ] + } + }, + "required": [ + "data", + "meta" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + } + } + }, + "/withdrawals/{withdrawalRoot}": { + "get": { + "operationId": "getWithdrawalByWithdrawalRoot", + "summary": "Retrieve withdrawal by withdrawal root", + "description": "Returns the withdrawal data by withdrawal root.", + "tags": [ + "Withdrawals" + ], + "parameters": [ + { + "in": "path", + "name": "withdrawalRoot", + "description": "The root hash of the withdrawal", + "schema": { + "type": "string", + "description": "The root hash of the withdrawal", + "example": "0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "The requested withdrawal record.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "withdrawalRoot": { + "type": "string", + "description": "The root hash of the withdrawal", + "example": "0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31" + }, + "nonce": { + "type": "number", + "description": "The nonce of the withdrawal", + "example": 0 + }, + "isCompleted": { + "type": "boolean", + "description": "Indicates if the withdrawal is completed", + "example": false + }, + "stakerAddress": { + "type": "string", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "delegatedTo": { + "type": "string", + "description": "The operator address to which staking is delegated", + "example": "0x0000000000000000000000000000000000000000" + }, + "withdrawerAddress": { + "type": "string", + "description": "The address of the withdrawer", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "shares": { + "type": "array", + "items": { + "type": "object", + "properties": { + "strategyAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0" + }, + "shares": { + "type": "string", + "description": "The amount of shares held in the strategy", + "example": "1277920000000000000000000" + } + }, + "required": [ + "strategyAddress", + "shares" + ] + }, + "description": "The list of strategy shares", + "example": [ + { + "strategyAddress": "0x93c4b944d05dfe6df7645a86cd2206016c51564d", + "shares": "1000288824523326631" + } + ] + }, + "startBlock": { + "type": "number", + "description": "The block number when the withdrawal was queued", + "example": 19912470 + }, + "createdAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was recorded by EigenExplorer", + "example": 19912470 + }, + "updatedAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was last updated", + "example": 19912470 + } + }, + "required": [ + "withdrawalRoot", + "nonce", + "isCompleted", + "stakerAddress", + "delegatedTo", + "withdrawerAddress", + "shares", + "startBlock", + "createdAtBlock", + "updatedAtBlock" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + } + } + }, + "/stakers": { + "get": { + "operationId": "getAllStakers", + "summary": "Retrieve all stakers", + "description": "Returns all staker records. This endpoint supports pagination.", + "tags": [ + "Stakers" + ], + "parameters": [ + { + "in": "query", + "name": "withTvl", + "description": "Toggle whether the route should calculate the TVL from shares", + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ], + "default": "false", + "description": "Toggle whether the route should calculate the TVL from shares", + "example": "false" + } + }, + { + "in": "query", + "name": "skip", + "description": "The number of records to skip for pagination", + "schema": { + "type": "string", + "default": "0", + "description": "The number of records to skip for pagination", + "example": 0 + } + }, + { + "in": "query", + "name": "take", + "description": "The number of records to return for pagination", + "schema": { + "type": "string", + "default": "12", + "description": "The number of records to return for pagination", + "example": 12 + } + } + ], + "responses": { + "200": { + "description": "The list of staker records.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the staker", + "example": "0x0000006c21964af0d420af8992851a30fa13a68b" + }, + "operatorAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the operator", + "example": "0x71c6f7ed8c2d4925d0baf16f6a85bb1736d412eb" + }, + "shares": { + "type": "array", + "items": { + "type": "object", + "properties": { + "stakerAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the staker", + "example": "0x0000006c21964af0d420af8992851a30fa13c68a" + }, + "strategyAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0x93c4b944d05dfe6df7645a86cd2206016c51564a" + }, + "shares": { + "type": "string", + "description": "The amount of shares held in the strategy", + "example": "40888428658906049" + } + }, + "required": [ + "stakerAddress", + "strategyAddress", + "shares" + ] + } + }, + "tvl": { + "type": "object", + "properties": { + "tvl": { + "type": "number", + "description": "The combined TVL of all restaking strategies in ETH", + "example": 1000000 + }, + "tvlBeaconChain": { + "type": "number", + "description": "The TVL of Beacon Chain restaking strategy in ETH", + "example": 1000000 + }, + "tvlRestaking": { + "type": "number", + "description": "The combined TVL of all liquid restaking strategies in ETH", + "example": 1000000 + }, + "tvlWETH": { + "type": "number", + "description": "The TVL of WETH restaking strategy in ETH", + "example": 1000000 + }, + "tvlStrategies": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in the strategy's native token", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in its native token", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in ETH", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } + } + }, + "required": [ + "tvl", + "tvlBeaconChain", + "tvlRestaking", + "tvlWETH", + "tvlStrategies", + "tvlStrategiesEth" + ], + "description": "The total value locked (TVL) in the AVS staker", + "example": { + "tvl": 1000000, + "tvlBeaconChain": 1000000, + "tvlWETH": 1000000, + "tvlRestaking": 1000000, + "tvlStrategies": { + "Eigen": 1000000, + "cbETH": 2000000 + }, + "tvlStrategiesEth": { + "stETH": 1000000, + "cbETH": 2000000 + } + } + } + }, + "required": [ + "address", + "operatorAddress", + "shares" + ] + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "number", + "description": "Total number of records in the database", + "example": 30 + }, + "skip": { + "type": "number", + "description": "The number of skiped records for this query", + "example": 0 + }, + "take": { + "type": "number", + "description": "The number of records returned for this query", + "example": 12 + } + }, + "required": [ + "total", + "skip", + "take" + ] + } + }, + "required": [ + "data", + "meta" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + } + } + }, + "/stakers/{address}": { + "get": { + "operationId": "getStakerByAddress", + "summary": "Retrieve a staker by address", + "description": "Returns a staker record by address.", + "tags": [ + "Stakers" + ], + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The address of the staker", + "schema": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the staker", + "example": "0x00107cfdeaddc0a3160ed2f6fedd627f313e7b1b" + }, + "required": true + }, + { + "in": "query", + "name": "withTvl", + "description": "Toggle whether the route should calculate the TVL from shares", + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ], + "default": "false", + "description": "Toggle whether the route should calculate the TVL from shares", + "example": "false" + } + } + ], + "responses": { + "200": { + "description": "The record of the requested operator.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the staker", + "example": "0x0000006c21964af0d420af8992851a30fa13a68b" + }, + "operatorAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the operator", + "example": "0x71c6f7ed8c2d4925d0baf16f6a85bb1736d412eb" + }, + "shares": { + "type": "array", + "items": { + "type": "object", + "properties": { + "stakerAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the staker", + "example": "0x0000006c21964af0d420af8992851a30fa13c68a" + }, + "strategyAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0x93c4b944d05dfe6df7645a86cd2206016c51564a" + }, + "shares": { + "type": "string", + "description": "The amount of shares held in the strategy", + "example": "40888428658906049" + } + }, + "required": [ + "stakerAddress", + "strategyAddress", + "shares" + ] + } + }, + "tvl": { + "type": "object", + "properties": { + "tvl": { + "type": "number", + "description": "The combined TVL of all restaking strategies in ETH", + "example": 1000000 + }, + "tvlBeaconChain": { + "type": "number", + "description": "The TVL of Beacon Chain restaking strategy in ETH", + "example": 1000000 + }, + "tvlRestaking": { + "type": "number", + "description": "The combined TVL of all liquid restaking strategies in ETH", + "example": 1000000 + }, + "tvlWETH": { + "type": "number", + "description": "The TVL of WETH restaking strategy in ETH", + "example": 1000000 + }, + "tvlStrategies": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in the strategy's native token", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in its native token", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } + }, + "tvlStrategiesEth": { + "type": "object", + "additionalProperties": { + "type": "number", + "description": "The total value locked (TVL) in the strategy, denominated in ETH", + "example": 1000000 + }, + "description": "The TVL of each individual restaking strategy in ETH", + "example": { + "Eigen": 1000000, + "cbETH": 2000000 + } + } + }, + "required": [ + "tvl", + "tvlBeaconChain", + "tvlRestaking", + "tvlWETH", + "tvlStrategies", + "tvlStrategiesEth" + ], + "description": "The total value locked (TVL) in the AVS staker", + "example": { + "tvl": 1000000, + "tvlBeaconChain": 1000000, + "tvlWETH": 1000000, + "tvlRestaking": 1000000, + "tvlStrategies": { + "Eigen": 1000000, + "cbETH": 2000000 + }, + "tvlStrategiesEth": { + "stETH": 1000000, + "cbETH": 2000000 + } + } + } + }, + "required": [ + "address", + "operatorAddress", + "shares" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + } + } + }, + "/stakers/{address}/withdrawals": { + "get": { + "operationId": "getStakerWithdrawals", + "summary": "Retrieve all withdrawals by staker address", + "description": "Returns all withdrawal data of the requested staker, including the withdrawal root, nonce, withdrawal status, and other relevant information.", + "tags": [ + "Stakers" + ], + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The address of the staker", + "schema": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "required": true + }, + { + "in": "query", + "name": "skip", + "description": "The number of records to skip for pagination", + "schema": { + "type": "string", + "default": "0", + "description": "The number of records to skip for pagination", + "example": 0 + } + }, + { + "in": "query", + "name": "take", + "description": "The number of records to return for pagination", + "schema": { + "type": "string", + "default": "12", + "description": "The number of records to return for pagination", + "example": 12 + } + } + ], + "responses": { + "200": { + "description": "The list of withdrawals.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "withdrawalRoot": { + "type": "string", + "description": "The root hash of the withdrawal", + "example": "0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31" + }, + "nonce": { + "type": "number", + "description": "The nonce of the withdrawal", + "example": 0 + }, + "isCompleted": { + "type": "boolean", + "description": "Indicates if the withdrawal is completed", + "example": false + }, + "stakerAddress": { + "type": "string", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "delegatedTo": { + "type": "string", + "description": "The operator address to which staking is delegated", + "example": "0x0000000000000000000000000000000000000000" + }, + "withdrawerAddress": { + "type": "string", + "description": "The address of the withdrawer", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "shares": { + "type": "array", + "items": { + "type": "object", + "properties": { + "strategyAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0" + }, + "shares": { + "type": "string", + "description": "The amount of shares held in the strategy", + "example": "1277920000000000000000000" + } + }, + "required": [ + "strategyAddress", + "shares" + ] + }, + "description": "The list of strategy shares", + "example": [ + { + "strategyAddress": "0x93c4b944d05dfe6df7645a86cd2206016c51564d", + "shares": "1000288824523326631" + } + ] + }, + "startBlock": { + "type": "number", + "description": "The block number when the withdrawal was queued", + "example": 19912470 + }, + "createdAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was recorded by EigenExplorer", + "example": 19912470 + }, + "updatedAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was last updated", + "example": 19912470 + } + }, + "required": [ + "withdrawalRoot", + "nonce", + "isCompleted", + "stakerAddress", + "delegatedTo", + "withdrawerAddress", + "shares", + "startBlock", + "createdAtBlock", + "updatedAtBlock" + ] + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "number", + "description": "Total number of records in the database", + "example": 30 + }, + "skip": { + "type": "number", + "description": "The number of skiped records for this query", + "example": 0 + }, + "take": { + "type": "number", + "description": "The number of records returned for this query", + "example": 12 + } + }, + "required": [ + "total", + "skip", + "take" + ] + } + }, + "required": [ + "data", + "meta" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + } + } + }, + "/stakers/{address}/withdrawals/queued": { + "get": { + "operationId": "getQueuedStakerWithdrawals", + "summary": "Retrieve queued withdrawals by staker address", + "description": "Returns all queued withdrawal data of the requested staker.", + "tags": [ + "Stakers" + ], + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The address of the staker", + "schema": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "required": true + }, + { + "in": "query", + "name": "skip", + "description": "The number of records to skip for pagination", + "schema": { + "type": "string", + "default": "0", + "description": "The number of records to skip for pagination", + "example": 0 + } + }, + { + "in": "query", + "name": "take", + "description": "The number of records to return for pagination", + "schema": { + "type": "string", + "default": "12", + "description": "The number of records to return for pagination", + "example": 12 + } + } + ], + "responses": { + "200": { + "description": "The list of queued withdrawals.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "withdrawalRoot": { + "type": "string", + "description": "The root hash of the withdrawal", + "example": "0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31" + }, + "nonce": { + "type": "number", + "description": "The nonce of the withdrawal", + "example": 0 + }, + "isCompleted": { + "type": "boolean", + "description": "Indicates if the withdrawal is completed", + "example": false + }, + "stakerAddress": { + "type": "string", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "delegatedTo": { + "type": "string", + "description": "The operator address to which staking is delegated", + "example": "0x0000000000000000000000000000000000000000" + }, + "withdrawerAddress": { + "type": "string", + "description": "The address of the withdrawer", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "shares": { + "type": "array", + "items": { + "type": "object", + "properties": { + "strategyAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0" + }, + "shares": { + "type": "string", + "description": "The amount of shares held in the strategy", + "example": "1277920000000000000000000" + } + }, + "required": [ + "strategyAddress", + "shares" + ] + }, + "description": "The list of strategy shares", + "example": [ + { + "strategyAddress": "0x93c4b944d05dfe6df7645a86cd2206016c51564d", + "shares": "1000288824523326631" + } + ] + }, + "startBlock": { + "type": "number", + "description": "The block number when the withdrawal was queued", + "example": 19912470 + }, + "createdAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was recorded by EigenExplorer", + "example": 19912470 + }, + "updatedAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was last updated", + "example": 19912470 + } + }, + "required": [ + "withdrawalRoot", + "nonce", + "isCompleted", + "stakerAddress", + "delegatedTo", + "withdrawerAddress", + "shares", + "startBlock", + "createdAtBlock", + "updatedAtBlock" + ] + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "number", + "description": "Total number of records in the database", + "example": 30 + }, + "skip": { + "type": "number", + "description": "The number of skiped records for this query", + "example": 0 + }, + "take": { + "type": "number", + "description": "The number of records returned for this query", + "example": 12 + } + }, + "required": [ + "total", + "skip", + "take" + ] + } + }, + "required": [ + "data", + "meta" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + } + } + }, + "/stakers/{address}/withdrawals/queued_withdrawable": { + "get": { + "operationId": "getQueuedWithdrawableStakerWithdrawals", + "summary": "Retrieve queued and withdrawable withdrawals by staker address", + "description": "Returns all queued and withdrawable withdrawal data of the requested staker.", + "tags": [ + "Stakers" + ], + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The address of the staker", + "schema": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "required": true + }, + { + "in": "query", + "name": "skip", + "description": "The number of records to skip for pagination", + "schema": { + "type": "string", + "default": "0", + "description": "The number of records to skip for pagination", + "example": 0 + } + }, + { + "in": "query", + "name": "take", + "description": "The number of records to return for pagination", + "schema": { + "type": "string", + "default": "12", + "description": "The number of records to return for pagination", + "example": 12 + } + } + ], + "responses": { + "200": { + "description": "The list of queued and withdrawable withdrawals.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "withdrawalRoot": { + "type": "string", + "description": "The root hash of the withdrawal", + "example": "0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31" + }, + "nonce": { + "type": "number", + "description": "The nonce of the withdrawal", + "example": 0 + }, + "isCompleted": { + "type": "boolean", + "description": "Indicates if the withdrawal is completed", + "example": false + }, + "stakerAddress": { + "type": "string", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "delegatedTo": { + "type": "string", + "description": "The operator address to which staking is delegated", + "example": "0x0000000000000000000000000000000000000000" + }, + "withdrawerAddress": { + "type": "string", + "description": "The address of the withdrawer", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "shares": { + "type": "array", + "items": { + "type": "object", + "properties": { + "strategyAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0" + }, + "shares": { + "type": "string", + "description": "The amount of shares held in the strategy", + "example": "1277920000000000000000000" + } + }, + "required": [ + "strategyAddress", + "shares" + ] + }, + "description": "The list of strategy shares", + "example": [ + { + "strategyAddress": "0x93c4b944d05dfe6df7645a86cd2206016c51564d", + "shares": "1000288824523326631" + } + ] + }, + "startBlock": { + "type": "number", + "description": "The block number when the withdrawal was queued", + "example": 19912470 + }, + "createdAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was recorded by EigenExplorer", + "example": 19912470 + }, + "updatedAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was last updated", + "example": 19912470 + } + }, + "required": [ + "withdrawalRoot", + "nonce", + "isCompleted", + "stakerAddress", + "delegatedTo", + "withdrawerAddress", + "shares", + "startBlock", + "createdAtBlock", + "updatedAtBlock" + ] + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "number", + "description": "Total number of records in the database", + "example": 30 + }, + "skip": { + "type": "number", + "description": "The number of skiped records for this query", + "example": 0 + }, + "take": { + "type": "number", + "description": "The number of records returned for this query", + "example": 12 + } + }, + "required": [ + "total", + "skip", + "take" + ] + } + }, + "required": [ + "data", + "meta" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + } + } + }, + "/stakers/{address}/withdrawals/completed": { + "get": { + "operationId": "getCompletedStakerWithdrawals", + "summary": "Retrieve completed withdrawals by staker address", + "description": "Returns all completed withdrawal data of the requested staker.", + "tags": [ + "Stakers" + ], + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The address of the staker", + "schema": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "required": true + }, + { + "in": "query", + "name": "skip", + "description": "The number of records to skip for pagination", + "schema": { + "type": "string", + "default": "0", + "description": "The number of records to skip for pagination", + "example": 0 + } + }, + { + "in": "query", + "name": "take", + "description": "The number of records to return for pagination", + "schema": { + "type": "string", + "default": "12", + "description": "The number of records to return for pagination", + "example": 12 + } + } + ], + "responses": { + "200": { + "description": "The list of completed withdrawals.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "withdrawalRoot": { + "type": "string", + "description": "The root hash of the withdrawal", + "example": "0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31" + }, + "nonce": { + "type": "number", + "description": "The nonce of the withdrawal", + "example": 0 + }, + "isCompleted": { + "type": "boolean", + "description": "Indicates if the withdrawal is completed", + "example": false + }, + "stakerAddress": { + "type": "string", + "description": "The address of the staker", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "delegatedTo": { + "type": "string", + "description": "The operator address to which staking is delegated", + "example": "0x0000000000000000000000000000000000000000" + }, + "withdrawerAddress": { + "type": "string", + "description": "The address of the withdrawer", + "example": "0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd" + }, + "shares": { + "type": "array", + "items": { + "type": "object", + "properties": { + "strategyAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The contract address of the restaking strategy", + "example": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0" + }, + "shares": { + "type": "string", + "description": "The amount of shares held in the strategy", + "example": "1277920000000000000000000" + } + }, + "required": [ + "strategyAddress", + "shares" + ] + }, + "description": "The list of strategy shares", + "example": [ + { + "strategyAddress": "0x93c4b944d05dfe6df7645a86cd2206016c51564d", + "shares": "1000288824523326631" + } + ] + }, + "startBlock": { + "type": "number", + "description": "The block number when the withdrawal was queued", + "example": 19912470 + }, + "createdAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was recorded by EigenExplorer", + "example": 19912470 + }, + "updatedAtBlock": { + "type": "number", + "description": "The block number when the withdrawal was last updated", + "example": 19912470 + } + }, + "required": [ + "withdrawalRoot", + "nonce", + "isCompleted", + "stakerAddress", + "delegatedTo", + "withdrawerAddress", + "shares", + "startBlock", + "createdAtBlock", + "updatedAtBlock" + ] + } + }, + "meta": { + "type": "object", + "properties": { + "total": { + "type": "number", + "description": "Total number of records in the database", + "example": 30 + }, + "skip": { + "type": "number", + "description": "The number of skiped records for this query", + "example": 0 + }, + "take": { + "type": "number", + "description": "The number of records returned for this query", + "example": 12 + } + }, + "required": [ + "total", + "skip", + "take" + ] + } + }, + "required": [ + "data", + "meta" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + } + } } }, "components": { diff --git a/packages/openapi/src/apiResponseSchema/avs/avsResponse.ts b/packages/openapi/src/apiResponseSchema/avs/avsResponse.ts index 7c9259ba..7f517234 100644 --- a/packages/openapi/src/apiResponseSchema/avs/avsResponse.ts +++ b/packages/openapi/src/apiResponseSchema/avs/avsResponse.ts @@ -57,6 +57,10 @@ export const AvsSchema = z.object({ Eigen: 1000000, cbETH: 2000000, }, + tvlStrategiesEth: { + stETH: 1000000, + cbETH: 2000000, + }, }, }), }); diff --git a/packages/openapi/src/apiResponseSchema/base/strategyTvlResponse.ts b/packages/openapi/src/apiResponseSchema/base/strategyTvlResponse.ts index fbbc7adc..3b99b9aa 100644 --- a/packages/openapi/src/apiResponseSchema/base/strategyTvlResponse.ts +++ b/packages/openapi/src/apiResponseSchema/base/strategyTvlResponse.ts @@ -7,6 +7,13 @@ const StrategyTvl = z ) .openapi({ example: 1000000 }); +const StrategyEthTvl = z + .number() + .describe( + 'The total value locked (TVL) in the strategy, denominated in ETH' + ) + .openapi({ example: 1000000 }); + const StrategyName = z .string() .describe('The name of the strategy') @@ -23,3 +30,15 @@ export const StrategyTvlSchema = z stETH: 2000000, }, }); + +export const StrategyEthTvlSchema = z + .record(StrategyName, StrategyEthTvl) + .describe( + 'The total value locked (TVL) in each restaking strategy, denominated in ETH' + ) + .openapi({ + example: { + cbETH: 1000000, + stETH: 2000000, + }, + }); diff --git a/packages/openapi/src/apiResponseSchema/base/tvlResponses.ts b/packages/openapi/src/apiResponseSchema/base/tvlResponses.ts index 7df5396b..ebecac90 100644 --- a/packages/openapi/src/apiResponseSchema/base/tvlResponses.ts +++ b/packages/openapi/src/apiResponseSchema/base/tvlResponses.ts @@ -1,5 +1,5 @@ import z from '../../../../api/src/schema/zod'; -import { StrategyTvlSchema } from './strategyTvlResponse'; +import { StrategyTvlSchema, StrategyEthTvlSchema } from './strategyTvlResponse'; export const TvlSchema = z.object({ tvl: z @@ -21,4 +21,7 @@ export const TvlSchema = z.object({ tvlStrategies: StrategyTvlSchema.describe( 'The TVL of each individual restaking strategy in its native token' ).openapi({ example: { Eigen: 1000000, cbETH: 2000000 } }), + tvlStrategiesEth: StrategyEthTvlSchema.describe( + 'The TVL of each individual restaking strategy in ETH' + ).openapi({ example: { Eigen: 1000000, cbETH: 2000000 } }), }); diff --git a/packages/openapi/src/apiResponseSchema/metrics/summaryMetricsResponse.ts b/packages/openapi/src/apiResponseSchema/metrics/summaryMetricsResponse.ts index cccf81dd..ee396a1a 100644 --- a/packages/openapi/src/apiResponseSchema/metrics/summaryMetricsResponse.ts +++ b/packages/openapi/src/apiResponseSchema/metrics/summaryMetricsResponse.ts @@ -1,5 +1,8 @@ import z from '../../../../api/src/schema/zod'; -import { StrategyTvlSchema } from '../base/strategyTvlResponse'; +import { + StrategyEthTvlSchema, + StrategyTvlSchema, +} from '../base/strategyTvlResponse'; export const SummaryMetricsResponseSchema = z.object({ tvl: z @@ -15,6 +18,7 @@ export const SummaryMetricsResponseSchema = z.object({ ) .openapi({ example: 1000000 }), tvlStrategies: StrategyTvlSchema, + tvlStrategiesEth: StrategyEthTvlSchema, tvlBeaconChain: z .number() .describe( diff --git a/packages/openapi/src/apiResponseSchema/operatorResponse.ts b/packages/openapi/src/apiResponseSchema/operatorResponse.ts index 26335906..94b53f5d 100644 --- a/packages/openapi/src/apiResponseSchema/operatorResponse.ts +++ b/packages/openapi/src/apiResponseSchema/operatorResponse.ts @@ -48,38 +48,10 @@ export const OperatorResponseSchema = z.object({ Eigen: 1000000, cbETH: 2000000, }, + tvlStrategiesEth: { + stETH: 1000000, + cbETH: 2000000, + }, }, }), }); - -// const OperatorSharesSchema = z.object({ -// strategyAddress: EthereumAddressSchema.describe( -// 'The contract address of the restaking strategy' -// ).openapi({ example: '0x298afb19a105d59e74658c4c334ff360bade6dd2' }), -// shares: z -// .string() -// .describe('The amount of shares held in the strategy for this operator') -// .openapi({ example: '40888428658906049' }), -// }); - -// export const IndividualOperatorResponseSchema = z.object({ -// address: EthereumAddressSchema.describe( -// 'The contract address of the AVS operator' -// ).openapi({ example: '0x09e6eb09213bdd3698bd8afb43ec3cb0ecff683a' }), -// metadataName: OperatorMetaDataSchema.shape.metadataName, -// metadataDescription: OperatorMetaDataSchema.shape.metadataDescription, -// metadataDiscord: OperatorMetaDataSchema.shape.metadataDiscord, -// metadataLogo: OperatorMetaDataSchema.shape.metadataLogo, -// metadataTelegram: OperatorMetaDataSchema.shape.metadataTelegram, -// metadataWebsite: OperatorMetaDataSchema.shape.metadataWebsite, -// metadataX: OperatorMetaDataSchema.shape.metadataX, -// shares: z.array(OperatorSharesSchema), -// tvl: z -// .number() -// .describe('The total value locked in the AVS operator') -// .openapi({ example: 1000000 }), -// totalStakers: z -// .number() -// .describe('The total number of stakers opted into the AVS operator') -// .openapi({ example: 10 }), -// }); diff --git a/packages/openapi/src/apiResponseSchema/stakerResponse.ts b/packages/openapi/src/apiResponseSchema/stakerResponse.ts index fc222d65..1dbba91c 100644 --- a/packages/openapi/src/apiResponseSchema/stakerResponse.ts +++ b/packages/openapi/src/apiResponseSchema/stakerResponse.ts @@ -1,18 +1,6 @@ import z from '../../../api/src/schema/zod'; import { EthereumAddressSchema } from '../../../api/src/schema/zod/schemas/base/ethereumAddress'; - -// { -// "address": "0x0000006c21964af0d420af8992851a30fa13c68b", -// "operatorAddress": "0x71c6f7ed8c2d4925d0baf16f6a85bb1736d412eb", -// "shares": [ -// { -// "stakerAddress": "0x0000006c21964af0d420af8992851a30fa13c68b", -// "strategyAddress": "0x93c4b944d05dfe6df7645a86cd2206016c51564d", -// "shares": "40888428658906049" -// } -// ], -// "tvl": 0.040888428658906045 -// }, +import { TvlSchema } from './base/tvlResponses'; export const StakerSharesSchema = z.object({ stakerAddress: EthereumAddressSchema.describe( @@ -35,8 +23,22 @@ export const StakerResponseSchema = z.object({ 'The address of the operator' ).openapi({ example: '0x71c6f7ed8c2d4925d0baf16f6a85bb1736d412eb' }), shares: z.array(StakerSharesSchema), - tvl: z - .number() - .describe('The total value locked in the AVS staker') - .openapi({ example: 0.040888428658906045 }), + tvl: TvlSchema.optional() + .describe('The total value locked (TVL) in the AVS staker') + .openapi({ + example: { + tvl: 1000000, + tvlBeaconChain: 1000000, + tvlWETH: 1000000, + tvlRestaking: 1000000, + tvlStrategies: { + Eigen: 1000000, + cbETH: 2000000, + }, + tvlStrategiesEth: { + stETH: 1000000, + cbETH: 2000000, + }, + }, + }), }); diff --git a/packages/openapi/src/apiResponseSchema/withdrawals/withdrawalsResponseSchema.ts b/packages/openapi/src/apiResponseSchema/withdrawals/withdrawalsResponseSchema.ts new file mode 100644 index 00000000..072db0a6 --- /dev/null +++ b/packages/openapi/src/apiResponseSchema/withdrawals/withdrawalsResponseSchema.ts @@ -0,0 +1,58 @@ +import z from '../../../../api/src/schema/zod'; +import { StrategySharesSchema } from '../../../../api/src/schema/zod/schemas/base/strategyShares'; + +export const WithdrawalsResponseSchema = z.object({ + withdrawalRoot: z + .string() + .describe('The root hash of the withdrawal') + .openapi({ + example: + '0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31', + }), + nonce: z + .number() + .describe('The nonce of the withdrawal') + .openapi({ example: 0 }), + isCompleted: z + .boolean() + .describe('Indicates if the withdrawal is completed') + .openapi({ example: false }), + stakerAddress: z + .string() + .describe('The address of the staker') + .openapi({ example: '0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd' }), + delegatedTo: z + .string() + .describe('The operator address to which staking is delegated') + .openapi({ example: '0x0000000000000000000000000000000000000000' }), + withdrawerAddress: z + .string() + .describe('The address of the withdrawer') + .openapi({ example: '0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd' }), + shares: z + .array(StrategySharesSchema) + .describe('The list of strategy shares') + .openapi({ + example: [ + { + strategyAddress: + '0x93c4b944d05dfe6df7645a86cd2206016c51564d', + shares: '1000288824523326631', + }, + ], + }), + startBlock: z + .number() + .describe('The block number when the withdrawal was queued') + .openapi({ example: 19912470 }), + createdAtBlock: z + .number() + .describe( + 'The block number when the withdrawal was recorded by EigenExplorer' + ) + .openapi({ example: 19912470 }), + updatedAtBlock: z + .number() + .describe('The block number when the withdrawal was last updated') + .openapi({ example: 19912470 }), +}); diff --git a/packages/openapi/src/documentBase.ts b/packages/openapi/src/documentBase.ts index e37aeac2..e5b32e00 100644 --- a/packages/openapi/src/documentBase.ts +++ b/packages/openapi/src/documentBase.ts @@ -1,8 +1,11 @@ import { openApiErrorResponses } from './apiResponseSchema/base/errorResponses'; import { createDocument } from 'zod-openapi'; +import { systemRoutes } from './routes/system'; import { avsRoutes } from './routes/avs'; import { metricsRoutes } from './routes/metrics'; import { operatorsRoutes } from './routes/operators'; +import { withdrawalsRoutes } from './routes/withdrawals'; +import { stakersRoutes } from './routes/stakers'; export const document = createDocument({ openapi: '3.0.3', @@ -23,9 +26,12 @@ export const document = createDocument({ }, ], paths: { + ...systemRoutes, ...metricsRoutes, ...avsRoutes, ...operatorsRoutes, + ...withdrawalsRoutes, + ...stakersRoutes, }, components: { schemas: {}, diff --git a/packages/openapi/src/routes/avs/getAllAvs.ts b/packages/openapi/src/routes/avs/getAllAvs.ts index 0055211e..ce9590ad 100644 --- a/packages/openapi/src/routes/avs/getAllAvs.ts +++ b/packages/openapi/src/routes/avs/getAllAvs.ts @@ -13,8 +13,8 @@ const AvsResponseSchema = z.object({ const CombinedQuerySchema = z .object({}) - .merge(PaginationQuerySchema) - .merge(WithTvlQuerySchema); + .merge(WithTvlQuerySchema) + .merge(PaginationQuerySchema); export const getAllAvs: ZodOpenApiOperationObject = { operationId: 'getAllAvs', diff --git a/packages/openapi/src/routes/avs/getAvsOperatorsByAddress.ts b/packages/openapi/src/routes/avs/getAvsOperatorsByAddress.ts index 2a4f98cf..813443a2 100644 --- a/packages/openapi/src/routes/avs/getAvsOperatorsByAddress.ts +++ b/packages/openapi/src/routes/avs/getAvsOperatorsByAddress.ts @@ -18,8 +18,8 @@ const AvsOperatorResponseSchema = z.object({ const CombinedQuerySchema = z .object({}) - .merge(PaginationQuerySchema) - .merge(WithTvlQuerySchema); + .merge(WithTvlQuerySchema) + .merge(PaginationQuerySchema); export const getAvsOperatorsByAddress: ZodOpenApiOperationObject = { operationId: 'getAvsOperatorsByAddress', diff --git a/packages/openapi/src/routes/avs/getAvsStakersByAddress.ts b/packages/openapi/src/routes/avs/getAvsStakersByAddress.ts index 077a4c91..ab8866a4 100644 --- a/packages/openapi/src/routes/avs/getAvsStakersByAddress.ts +++ b/packages/openapi/src/routes/avs/getAvsStakersByAddress.ts @@ -5,11 +5,17 @@ import { ZodOpenApiOperationObject } from 'zod-openapi'; import { PaginationQuerySchema } from '../../../../api/src/schema/zod/schemas/paginationQuery'; import { PaginationMetaResponsesSchema } from '../../apiResponseSchema/base/paginationMetaResponses'; import { StakerResponseSchema } from '../../apiResponseSchema/stakerResponse'; +import { WithTvlQuerySchema } from '../../../../api/src/schema/zod/schemas/withTvlQuery'; const EthereumAddressParam = z.object({ address: EthereumAddressSchema, }); +const CombinedQuerySchema = z + .object({}) + .merge(WithTvlQuerySchema) + .merge(PaginationQuerySchema); + const AvsStakerResponseSchema = z.object({ data: z.array(StakerResponseSchema), meta: PaginationMetaResponsesSchema, @@ -23,7 +29,7 @@ export const getAvsStakersByAddress: ZodOpenApiOperationObject = { tags: ['AVS'], requestParams: { path: EthereumAddressParam, - query: PaginationQuerySchema, + query: CombinedQuerySchema, }, responses: { '200': { diff --git a/packages/openapi/src/routes/metrics/getTvlRestaking.ts b/packages/openapi/src/routes/metrics/getTvlRestaking.ts index 2ccd30e7..807dc853 100644 --- a/packages/openapi/src/routes/metrics/getTvlRestaking.ts +++ b/packages/openapi/src/routes/metrics/getTvlRestaking.ts @@ -2,7 +2,10 @@ import { ZodOpenApiOperationObject } from 'zod-openapi'; import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; import { TvlResponseSchema } from '../../apiResponseSchema/metrics/tvlResponse'; import z from '../../../../api/src/schema/zod'; -import { StrategyTvlSchema } from '../../apiResponseSchema/base/strategyTvlResponse'; +import { + StrategyTvlSchema, + StrategyEthTvlSchema, +} from '../../apiResponseSchema/base/strategyTvlResponse'; const RestakingTvlResponseSchema = TvlResponseSchema.extend({ tvl: z @@ -10,6 +13,7 @@ const RestakingTvlResponseSchema = TvlResponseSchema.extend({ .describe('The value of the combined restaking strategy TVL in ETH') .openapi({ example: 1000000 }), tvlStrategies: StrategyTvlSchema, + tvlStrategiesEth: StrategyEthTvlSchema, }); export const getRestakingTvlMetrics: ZodOpenApiOperationObject = { diff --git a/packages/openapi/src/routes/operators/getAllOperators.ts b/packages/openapi/src/routes/operators/getAllOperators.ts index 2a37779f..91587f1b 100644 --- a/packages/openapi/src/routes/operators/getAllOperators.ts +++ b/packages/openapi/src/routes/operators/getAllOperators.ts @@ -13,8 +13,8 @@ const AllOperatorsResponseSchema = z.object({ const CombinedQuerySchema = z .object({}) - .merge(PaginationQuerySchema) - .merge(WithTvlQuerySchema); + .merge(WithTvlQuerySchema) + .merge(PaginationQuerySchema); export const getAllOperators: ZodOpenApiOperationObject = { operationId: 'getAllOperators', @@ -27,7 +27,7 @@ export const getAllOperators: ZodOpenApiOperationObject = { }, responses: { '200': { - description: 'The list of Operators records.', + description: 'The list of operator records.', content: { 'application/json': { schema: AllOperatorsResponseSchema, diff --git a/packages/openapi/src/routes/operators/getOperatorByAddress.ts b/packages/openapi/src/routes/operators/getOperatorByAddress.ts index 19cbf396..ebd3c131 100644 --- a/packages/openapi/src/routes/operators/getOperatorByAddress.ts +++ b/packages/openapi/src/routes/operators/getOperatorByAddress.ts @@ -7,7 +7,7 @@ import { WithTvlQuerySchema } from '../../../../api/src/schema/zod/schemas/withT const OperatorAddressParam = z.object({ address: EthereumAddressSchema.describe( - 'The address of the operator.' + 'The address of the operator' ).openapi({ example: '0x00107cfdeaddc0a3160ed2f6fedd627f313e7b1b' }), }); diff --git a/packages/openapi/src/routes/stakers/getAllStakers.ts b/packages/openapi/src/routes/stakers/getAllStakers.ts new file mode 100644 index 00000000..3004a3fd --- /dev/null +++ b/packages/openapi/src/routes/stakers/getAllStakers.ts @@ -0,0 +1,39 @@ +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import z from '../../../../api/src/schema/zod'; +import { PaginationQuerySchema } from '../../../../api/src/schema/zod/schemas/paginationQuery'; +import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; +import { PaginationMetaResponsesSchema } from '../../apiResponseSchema/base/paginationMetaResponses'; +import { WithTvlQuerySchema } from '../../../../api/src/schema/zod/schemas/withTvlQuery'; +import { StakerResponseSchema } from '../../apiResponseSchema/stakerResponse'; + +const AllStakersResponseSchema = z.object({ + data: z.array(StakerResponseSchema), + meta: PaginationMetaResponsesSchema, +}); + +const CombinedQuerySchema = z + .object({}) + .merge(WithTvlQuerySchema) + .merge(PaginationQuerySchema); + +export const getAllStakers: ZodOpenApiOperationObject = { + operationId: 'getAllStakers', + summary: 'Retrieve all stakers', + description: + 'Returns all staker records. This endpoint supports pagination.', + tags: ['Stakers'], + requestParams: { + query: CombinedQuerySchema, + }, + responses: { + '200': { + description: 'The list of staker records.', + content: { + 'application/json': { + schema: AllStakersResponseSchema, + }, + }, + }, + ...openApiErrorResponses, + }, +}; diff --git a/packages/openapi/src/routes/stakers/getCompletedStakerWithdrawals.ts b/packages/openapi/src/routes/stakers/getCompletedStakerWithdrawals.ts new file mode 100644 index 00000000..ade58f9f --- /dev/null +++ b/packages/openapi/src/routes/stakers/getCompletedStakerWithdrawals.ts @@ -0,0 +1,41 @@ +import z from '../../../../api/src/schema/zod'; +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; +import { PaginationMetaResponsesSchema } from '../../apiResponseSchema/base/paginationMetaResponses'; +import { PaginationQuerySchema } from '../../../../api/src/schema/zod/schemas/paginationQuery'; +import { WithdrawalsResponseSchema } from '../../apiResponseSchema/withdrawals/withdrawalsResponseSchema'; +import { EthereumAddressSchema } from '../../../../api/src/schema/zod/schemas/base/ethereumAddress'; + +const WithdrawalsResponseSchemaWithMeta = z.object({ + data: z.array(WithdrawalsResponseSchema), + meta: PaginationMetaResponsesSchema, +}); + +const StakerAddressParam = z.object({ + address: EthereumAddressSchema.describe( + 'The address of the staker' + ).openapi({ example: '0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd' }), +}); + +export const getCompletedStakerWithdrawals: ZodOpenApiOperationObject = { + operationId: 'getCompletedStakerWithdrawals', + summary: 'Retrieve completed withdrawals by staker address', + description: + 'Returns all completed withdrawal data of the requested staker.', + tags: ['Stakers'], + requestParams: { + path: StakerAddressParam, + query: PaginationQuerySchema, + }, + responses: { + '200': { + description: 'The list of completed withdrawals.', + content: { + 'application/json': { + schema: WithdrawalsResponseSchemaWithMeta, + }, + }, + }, + ...openApiErrorResponses, + }, +}; diff --git a/packages/openapi/src/routes/stakers/getQueuedStakerWithdrawals.ts b/packages/openapi/src/routes/stakers/getQueuedStakerWithdrawals.ts new file mode 100644 index 00000000..4c012e19 --- /dev/null +++ b/packages/openapi/src/routes/stakers/getQueuedStakerWithdrawals.ts @@ -0,0 +1,40 @@ +import z from '../../../../api/src/schema/zod'; +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; +import { PaginationQuerySchema } from '../../../../api/src/schema/zod/schemas/paginationQuery'; +import { PaginationMetaResponsesSchema } from '../../apiResponseSchema/base/paginationMetaResponses'; +import { WithdrawalsResponseSchema } from '../../apiResponseSchema/withdrawals/withdrawalsResponseSchema'; +import { EthereumAddressSchema } from '../../../../api/src/schema/zod/schemas/base/ethereumAddress'; + +const WithdrawalsResponseSchemaWithMeta = z.object({ + data: z.array(WithdrawalsResponseSchema), + meta: PaginationMetaResponsesSchema, +}); + +const StakerAddressParam = z.object({ + address: EthereumAddressSchema.describe( + 'The address of the staker' + ).openapi({ example: '0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd' }), +}); + +export const getQueuedStakerWithdrawals: ZodOpenApiOperationObject = { + operationId: 'getQueuedStakerWithdrawals', + summary: 'Retrieve queued withdrawals by staker address', + description: 'Returns all queued withdrawal data of the requested staker.', + tags: ['Stakers'], + requestParams: { + path: StakerAddressParam, + query: PaginationQuerySchema, + }, + responses: { + '200': { + description: 'The list of queued withdrawals.', + content: { + 'application/json': { + schema: WithdrawalsResponseSchemaWithMeta, + }, + }, + }, + ...openApiErrorResponses, + }, +}; diff --git a/packages/openapi/src/routes/stakers/getQueuedWithdrawableStakerWithdrawals.ts b/packages/openapi/src/routes/stakers/getQueuedWithdrawableStakerWithdrawals.ts new file mode 100644 index 00000000..252c3c2c --- /dev/null +++ b/packages/openapi/src/routes/stakers/getQueuedWithdrawableStakerWithdrawals.ts @@ -0,0 +1,43 @@ +import z from '../../../../api/src/schema/zod'; +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; +import { PaginationQuerySchema } from '../../../../api/src/schema/zod/schemas/paginationQuery'; +import { PaginationMetaResponsesSchema } from '../../apiResponseSchema/base/paginationMetaResponses'; +import { WithdrawalsResponseSchema } from '../../apiResponseSchema/withdrawals/withdrawalsResponseSchema'; +import { EthereumAddressSchema } from '../../../../api/src/schema/zod/schemas/base/ethereumAddress'; + +const WithdrawalsResponseSchemaWithMeta = z.object({ + data: z.array(WithdrawalsResponseSchema), + meta: PaginationMetaResponsesSchema, +}); + +const StakerAddressParam = z.object({ + address: EthereumAddressSchema.describe( + 'The address of the staker' + ).openapi({ example: '0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd' }), +}); + +export const getQueuedWithdrawableStakerWithdrawals: ZodOpenApiOperationObject = + { + operationId: 'getQueuedWithdrawableStakerWithdrawals', + summary: + 'Retrieve queued and withdrawable withdrawals by staker address', + description: + 'Returns all queued and withdrawable withdrawal data of the requested staker.', + tags: ['Stakers'], + requestParams: { + path: StakerAddressParam, + query: PaginationQuerySchema, + }, + responses: { + '200': { + description: 'The list of queued and withdrawable withdrawals.', + content: { + 'application/json': { + schema: WithdrawalsResponseSchemaWithMeta, + }, + }, + }, + ...openApiErrorResponses, + }, + }; diff --git a/packages/openapi/src/routes/stakers/getStakerByAddress.ts b/packages/openapi/src/routes/stakers/getStakerByAddress.ts new file mode 100644 index 00000000..c034f577 --- /dev/null +++ b/packages/openapi/src/routes/stakers/getStakerByAddress.ts @@ -0,0 +1,34 @@ +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; +import z from '../../../../api/src/schema/zod'; +import { EthereumAddressSchema } from '../../../../api/src/schema/zod/schemas/base/ethereumAddress'; +import { WithTvlQuerySchema } from '../../../../api/src/schema/zod/schemas/withTvlQuery'; +import { StakerResponseSchema } from '../../apiResponseSchema/stakerResponse'; + +const StakerAddressParam = z.object({ + address: EthereumAddressSchema.describe( + 'The address of the staker' + ).openapi({ example: '0x00107cfdeaddc0a3160ed2f6fedd627f313e7b1b' }), +}); + +export const getStakerByAddress: ZodOpenApiOperationObject = { + operationId: 'getStakerByAddress', + summary: 'Retrieve a staker by address', + description: 'Returns a staker record by address.', + tags: ['Stakers'], + requestParams: { + query: WithTvlQuerySchema, + path: StakerAddressParam, + }, + responses: { + '200': { + description: 'The record of the requested operator.', + content: { + 'application/json': { + schema: StakerResponseSchema, + }, + }, + }, + ...openApiErrorResponses, + }, +}; diff --git a/packages/openapi/src/routes/stakers/getStakerWithdrawals.ts b/packages/openapi/src/routes/stakers/getStakerWithdrawals.ts new file mode 100644 index 00000000..27cd142b --- /dev/null +++ b/packages/openapi/src/routes/stakers/getStakerWithdrawals.ts @@ -0,0 +1,41 @@ +import z from '../../../../api/src/schema/zod'; +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; +import { PaginationQuerySchema } from '../../../../api/src/schema/zod/schemas/paginationQuery'; +import { PaginationMetaResponsesSchema } from '../../apiResponseSchema/base/paginationMetaResponses'; +import { WithdrawalsResponseSchema } from '../../apiResponseSchema/withdrawals/withdrawalsResponseSchema'; +import { EthereumAddressSchema } from '../../../../api/src/schema/zod/schemas/base/ethereumAddress'; + +const WithdrawalsResponseSchemaWithMeta = z.object({ + data: z.array(WithdrawalsResponseSchema), + meta: PaginationMetaResponsesSchema, +}); + +const StakerAddressParam = z.object({ + address: EthereumAddressSchema.describe( + 'The address of the staker' + ).openapi({ example: '0x74ede5f75247fbdb9266d2b3a7be63b3db7611dd' }), +}); + +export const getStakerWithdrawals: ZodOpenApiOperationObject = { + operationId: 'getStakerWithdrawals', + summary: 'Retrieve all withdrawals by staker address', + description: + 'Returns all withdrawal data of the requested staker, including the withdrawal root, nonce, withdrawal status, and other relevant information.', + tags: ['Stakers'], + requestParams: { + path: StakerAddressParam, + query: PaginationQuerySchema, + }, + responses: { + '200': { + description: 'The list of withdrawals.', + content: { + 'application/json': { + schema: WithdrawalsResponseSchemaWithMeta, + }, + }, + }, + ...openApiErrorResponses, + }, +}; diff --git a/packages/openapi/src/routes/stakers/index.ts b/packages/openapi/src/routes/stakers/index.ts new file mode 100644 index 00000000..8162400a --- /dev/null +++ b/packages/openapi/src/routes/stakers/index.ts @@ -0,0 +1,24 @@ +import { ZodOpenApiPathsObject } from 'zod-openapi'; +import { getAllStakers } from './getAllStakers'; +import { getStakerByAddress } from './getStakerByAddress'; +import { getStakerWithdrawals } from './getStakerWithdrawals'; +import { getQueuedStakerWithdrawals } from './getQueuedStakerWithdrawals'; +import { getQueuedWithdrawableStakerWithdrawals } from './getQueuedWithdrawableStakerWithdrawals'; +import { getCompletedStakerWithdrawals } from './getCompletedStakerWithdrawals'; + +export const stakersRoutes: ZodOpenApiPathsObject = { + '/stakers': { get: getAllStakers }, + '/stakers/{address}': { get: getStakerByAddress }, + '/stakers/{address}/withdrawals': { + get: getStakerWithdrawals, + }, + '/stakers/{address}/withdrawals/queued': { + get: getQueuedStakerWithdrawals, + }, + '/stakers/{address}/withdrawals/queued_withdrawable': { + get: getQueuedWithdrawableStakerWithdrawals, + }, + '/stakers/{address}/withdrawals/completed': { + get: getCompletedStakerWithdrawals, + }, +}; diff --git a/packages/openapi/src/routes/system/getHealth.ts b/packages/openapi/src/routes/system/getHealth.ts new file mode 100644 index 00000000..f87f65ec --- /dev/null +++ b/packages/openapi/src/routes/system/getHealth.ts @@ -0,0 +1,27 @@ +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import z from '../../../../api/src/schema/zod'; + +const HealthResponseSchema = z.object({ + status: z + .string() + .describe('The status of the API server') + .openapi({ example: 'ok' }), +}); + +export const getHealth: ZodOpenApiOperationObject = { + operationId: 'getHealth', + summary: 'Retrieve API server status', + description: 'Returns API server status.', + tags: ['System'], + requestParams: {}, + responses: { + '200': { + description: 'The API server status.', + content: { + 'application/json': { + schema: HealthResponseSchema, + }, + }, + }, + }, +}; diff --git a/packages/openapi/src/routes/system/getVersion.ts b/packages/openapi/src/routes/system/getVersion.ts new file mode 100644 index 00000000..79fc6a29 --- /dev/null +++ b/packages/openapi/src/routes/system/getVersion.ts @@ -0,0 +1,27 @@ +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import z from '../../../../api/src/schema/zod'; + +const VersionResponseSchema = z.object({ + version: z + .string() + .describe('The version of the API server') + .openapi({ example: 'v0.0.1' }), +}); + +export const getVersion: ZodOpenApiOperationObject = { + operationId: 'getVersion', + summary: 'Retrieve API server version', + description: 'Returns API server version.', + tags: ['System'], + requestParams: {}, + responses: { + '200': { + description: 'The API server version.', + content: { + 'application/json': { + schema: VersionResponseSchema, + }, + }, + }, + }, +}; diff --git a/packages/openapi/src/routes/system/index.ts b/packages/openapi/src/routes/system/index.ts new file mode 100644 index 00000000..fbe39b1e --- /dev/null +++ b/packages/openapi/src/routes/system/index.ts @@ -0,0 +1,10 @@ +import { ZodOpenApiPathsObject } from 'zod-openapi'; +import { getHealth } from './getHealth'; +import { getVersion } from './getVersion'; + +export const systemRoutes: ZodOpenApiPathsObject = { + '/version': { get: getVersion }, + '/health': { + get: getHealth, + }, +}; diff --git a/packages/openapi/src/routes/withdrawals/getAllWithdrawals.ts b/packages/openapi/src/routes/withdrawals/getAllWithdrawals.ts new file mode 100644 index 00000000..085f2972 --- /dev/null +++ b/packages/openapi/src/routes/withdrawals/getAllWithdrawals.ts @@ -0,0 +1,39 @@ +import z from '../../../../api/src/schema/zod'; +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; +import { PaginationQuerySchema } from '../../../../api/src/schema/zod/schemas/paginationQuery'; +import { WithdrawalListQuerySchema } from '../../../../api/src/schema/zod/schemas/withdrawal'; +import { PaginationMetaResponsesSchema } from '../../apiResponseSchema/base/paginationMetaResponses'; +import { WithdrawalsResponseSchema } from '../../apiResponseSchema/withdrawals/withdrawalsResponseSchema'; + +const WithdrawalsResponseSchemaWithMeta = z.object({ + data: z.array(WithdrawalsResponseSchema), + meta: PaginationMetaResponsesSchema, +}); + +const CombinedQuerySchema = z + .object({}) + .merge(WithdrawalListQuerySchema) + .merge(PaginationQuerySchema); + +export const getAllWithdrawals: ZodOpenApiOperationObject = { + operationId: 'getAllWithdrawals', + summary: 'Retrieve all withdrawals', + description: + 'Returns all withdrawal data, including the withdrawal root, nonce, withdrawal status, and other relevant information.', + tags: ['Withdrawals'], + requestParams: { + query: CombinedQuerySchema, + }, + responses: { + '200': { + description: 'The list of withdrawals.', + content: { + 'application/json': { + schema: WithdrawalsResponseSchemaWithMeta, + }, + }, + }, + ...openApiErrorResponses, + }, +}; diff --git a/packages/openapi/src/routes/withdrawals/getWithdralByWithdrawalRoot.ts b/packages/openapi/src/routes/withdrawals/getWithdralByWithdrawalRoot.ts new file mode 100644 index 00000000..9177ac62 --- /dev/null +++ b/packages/openapi/src/routes/withdrawals/getWithdralByWithdrawalRoot.ts @@ -0,0 +1,35 @@ +import z from '../../../../api/src/schema/zod'; +import { ZodOpenApiOperationObject } from 'zod-openapi'; +import { openApiErrorResponses } from '../../apiResponseSchema/base/errorResponses'; +import { WithdrawalsResponseSchema } from '../../apiResponseSchema/withdrawals/withdrawalsResponseSchema'; + +const WithdrawalRootParam = z.object({ + withdrawalRoot: z + .string() + .describe('The root hash of the withdrawal') + .openapi({ + example: + '0x9e6728ef0a8ad6009107a886047aae35bc5ed7deaa68580b0d1f8f67e3e5ed31', + }), +}); + +export const getWithdrawalByWithdrawalRoot: ZodOpenApiOperationObject = { + operationId: 'getWithdrawalByWithdrawalRoot', + summary: 'Retrieve withdrawal by withdrawal root', + description: 'Returns the withdrawal data by withdrawal root.', + tags: ['Withdrawals'], + requestParams: { + path: WithdrawalRootParam, + }, + responses: { + '200': { + description: 'The requested withdrawal record.', + content: { + 'application/json': { + schema: WithdrawalsResponseSchema, + }, + }, + }, + ...openApiErrorResponses, + }, +}; diff --git a/packages/openapi/src/routes/withdrawals/index.ts b/packages/openapi/src/routes/withdrawals/index.ts new file mode 100644 index 00000000..ba1bd525 --- /dev/null +++ b/packages/openapi/src/routes/withdrawals/index.ts @@ -0,0 +1,12 @@ +import { ZodOpenApiPathsObject } from 'zod-openapi'; +import { getAllWithdrawals } from './getAllWithdrawals'; +import { getWithdrawalByWithdrawalRoot } from './getWithdralByWithdrawalRoot'; + +export const withdrawalsRoutes: ZodOpenApiPathsObject = { + '/withdrawals': { + get: getAllWithdrawals, + }, + '/withdrawals/{withdrawalRoot}': { + get: getWithdrawalByWithdrawalRoot, + }, +}; diff --git a/packages/prisma/migrations/20240523121211_include_created_updated_block_ts/migration.sql b/packages/prisma/migrations/20240523121211_include_created_updated_block_ts/migration.sql new file mode 100644 index 00000000..4b86e5dc --- /dev/null +++ b/packages/prisma/migrations/20240523121211_include_created_updated_block_ts/migration.sql @@ -0,0 +1,29 @@ +-- AlterTable +ALTER TABLE "Avs" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "createdAtBlock" BIGINT NOT NULL DEFAULT 0, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedAtBlock" BIGINT NOT NULL DEFAULT 0; + +-- AlterTable +ALTER TABLE "Operator" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "createdAtBlock" BIGINT NOT NULL DEFAULT 0, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedAtBlock" BIGINT NOT NULL DEFAULT 0; + +-- AlterTable +ALTER TABLE "Pod" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "createdAtBlock" BIGINT NOT NULL DEFAULT 0, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedAtBlock" BIGINT NOT NULL DEFAULT 0; + +-- AlterTable +ALTER TABLE "Staker" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "createdAtBlock" BIGINT NOT NULL DEFAULT 0, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedAtBlock" BIGINT NOT NULL DEFAULT 0; + +-- AlterTable +ALTER TABLE "Withdrawal" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ALTER COLUMN "createdAtBlock" SET DEFAULT 0, +ALTER COLUMN "updatedAtBlock" SET DEFAULT 0; diff --git a/packages/prisma/migrations/20240603221416_include_raw_evm_logs_data/migration.sql b/packages/prisma/migrations/20240603221416_include_raw_evm_logs_data/migration.sql new file mode 100644 index 00000000..c088afaf --- /dev/null +++ b/packages/prisma/migrations/20240603221416_include_raw_evm_logs_data/migration.sql @@ -0,0 +1,148 @@ +-- CreateTable +CREATE TABLE "EventLogs_AVSMetadataURIUpdated" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "avs" TEXT NOT NULL, + "metadataURI" TEXT NOT NULL, + + CONSTRAINT "EventLogs_AVSMetadataURIUpdated_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateTable +CREATE TABLE "EventLogs_OperatorMetadataURIUpdated" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "operator" TEXT NOT NULL, + "metadataURI" TEXT NOT NULL, + + CONSTRAINT "EventLogs_OperatorMetadataURIUpdated_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateTable +CREATE TABLE "EventLogs_OperatorAVSRegistrationStatusUpdated" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "operator" TEXT NOT NULL, + "avs" TEXT NOT NULL, + "status" INTEGER NOT NULL, + + CONSTRAINT "EventLogs_OperatorAVSRegistrationStatusUpdated_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateTable +CREATE TABLE "EventLogs_PodDeployed" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "eigenPod" TEXT NOT NULL, + "podOwner" TEXT NOT NULL, + + CONSTRAINT "EventLogs_PodDeployed_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateTable +CREATE TABLE "EventLogs_StakerDelegated" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "staker" TEXT NOT NULL, + "operator" TEXT NOT NULL, + + CONSTRAINT "EventLogs_StakerDelegated_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateTable +CREATE TABLE "EventLogs_StakerUndelegated" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "staker" TEXT NOT NULL, + "operator" TEXT NOT NULL, + + CONSTRAINT "EventLogs_StakerUndelegated_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateTable +CREATE TABLE "EventLogs_OperatorSharesIncreased" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "staker" TEXT NOT NULL, + "operator" TEXT NOT NULL, + "strategy" TEXT NOT NULL, + "shares" TEXT NOT NULL, + + CONSTRAINT "EventLogs_OperatorSharesIncreased_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateTable +CREATE TABLE "EventLogs_OperatorSharesDecreased" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "staker" TEXT NOT NULL, + "operator" TEXT NOT NULL, + "strategy" TEXT NOT NULL, + "shares" TEXT NOT NULL, + + CONSTRAINT "EventLogs_OperatorSharesDecreased_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateTable +CREATE TABLE "Evm_BlockData" ( + "number" BIGINT NOT NULL, + "timestamp" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Evm_BlockData_pkey" PRIMARY KEY ("number") +); + +-- CreateIndex +CREATE INDEX "EventLogs_AVSMetadataURIUpdated_avs_idx" ON "EventLogs_AVSMetadataURIUpdated"("avs"); + +-- CreateIndex +CREATE INDEX "EventLogs_OperatorMetadataURIUpdated_operator_idx" ON "EventLogs_OperatorMetadataURIUpdated"("operator"); + +-- CreateIndex +CREATE INDEX "EventLogs_OperatorAVSRegistrationStatusUpdated_operator_avs_idx" ON "EventLogs_OperatorAVSRegistrationStatusUpdated"("operator", "avs"); + +-- CreateIndex +CREATE INDEX "EventLogs_PodDeployed_eigenPod_podOwner_idx" ON "EventLogs_PodDeployed"("eigenPod", "podOwner"); + +-- CreateIndex +CREATE INDEX "EventLogs_StakerDelegated_staker_operator_idx" ON "EventLogs_StakerDelegated"("staker", "operator"); + +-- CreateIndex +CREATE INDEX "EventLogs_StakerUndelegated_staker_operator_idx" ON "EventLogs_StakerUndelegated"("staker", "operator"); + +-- CreateIndex +CREATE INDEX "EventLogs_OperatorSharesIncreased_staker_operator_idx" ON "EventLogs_OperatorSharesIncreased"("staker", "operator"); + +-- CreateIndex +CREATE INDEX "EventLogs_OperatorSharesDecreased_staker_operator_idx" ON "EventLogs_OperatorSharesDecreased"("staker", "operator"); diff --git a/packages/prisma/migrations/20240606213654_include_raw_evm_logs_data_withdrawal/migration.sql b/packages/prisma/migrations/20240606213654_include_raw_evm_logs_data_withdrawal/migration.sql new file mode 100644 index 00000000..143c6afa --- /dev/null +++ b/packages/prisma/migrations/20240606213654_include_raw_evm_logs_data_withdrawal/migration.sql @@ -0,0 +1,22 @@ +-- CreateTable +CREATE TABLE "EventLogs_WithdrawalQueued" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "withdrawalRoot" TEXT NOT NULL, + "staker" TEXT NOT NULL, + "delegatedTo" TEXT NOT NULL, + "withdrawer" TEXT NOT NULL, + "nonce" BIGINT NOT NULL, + "startBlock" BIGINT NOT NULL, + "strategies" TEXT[], + "shares" TEXT[], + + CONSTRAINT "EventLogs_WithdrawalQueued_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateIndex +CREATE INDEX "EventLogs_WithdrawalQueued_withdrawalRoot_idx" ON "EventLogs_WithdrawalQueued"("withdrawalRoot"); diff --git a/packages/prisma/migrations/20240606215914_include_raw_evm_logs_data_withdrawal_completed/migration.sql b/packages/prisma/migrations/20240606215914_include_raw_evm_logs_data_withdrawal_completed/migration.sql new file mode 100644 index 00000000..96536d5e --- /dev/null +++ b/packages/prisma/migrations/20240606215914_include_raw_evm_logs_data_withdrawal_completed/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "EventLogs_WithdrawalCompleted" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "withdrawalRoot" TEXT NOT NULL, + + CONSTRAINT "EventLogs_WithdrawalCompleted_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateIndex +CREATE INDEX "EventLogs_WithdrawalCompleted_withdrawalRoot_idx" ON "EventLogs_WithdrawalCompleted"("withdrawalRoot"); diff --git a/packages/prisma/migrations/20240610150322_include_metadata_url_avs_operator/migration.sql b/packages/prisma/migrations/20240610150322_include_metadata_url_avs_operator/migration.sql new file mode 100644 index 00000000..2d34d700 --- /dev/null +++ b/packages/prisma/migrations/20240610150322_include_metadata_url_avs_operator/migration.sql @@ -0,0 +1,7 @@ +-- AlterTable +ALTER TABLE "Avs" ADD COLUMN "isMetadataSynced" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "metadataUrl" TEXT; + +-- AlterTable +ALTER TABLE "Operator" ADD COLUMN "isMetadataSynced" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "metadataUrl" TEXT; diff --git a/packages/prisma/migrations/20240610150614_include_deposit_logs_data/migration.sql b/packages/prisma/migrations/20240610150614_include_deposit_logs_data/migration.sql new file mode 100644 index 00000000..bdd02dba --- /dev/null +++ b/packages/prisma/migrations/20240610150614_include_deposit_logs_data/migration.sql @@ -0,0 +1,34 @@ +-- CreateTable +CREATE TABLE "Deposit" ( + "transactionHash" TEXT NOT NULL, + "stakerAddress" TEXT NOT NULL, + "tokenAddress" TEXT NOT NULL, + "strategyAddress" TEXT NOT NULL, + "shares" TEXT NOT NULL, + "createdAtBlock" BIGINT NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Deposit_pkey" PRIMARY KEY ("transactionHash") +); + +-- CreateTable +CREATE TABLE "EventLogs_Deposit" ( + "address" TEXT NOT NULL, + "transactionHash" TEXT NOT NULL, + "transactionIndex" INTEGER NOT NULL, + "blockNumber" BIGINT NOT NULL, + "blockHash" TEXT NOT NULL, + "blockTime" TIMESTAMP(3) NOT NULL, + "staker" TEXT NOT NULL, + "token" TEXT NOT NULL, + "strategy" TEXT NOT NULL, + "shares" TEXT NOT NULL, + + CONSTRAINT "EventLogs_Deposit_pkey" PRIMARY KEY ("transactionHash","transactionIndex") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Deposit_transactionHash_key" ON "Deposit"("transactionHash"); + +-- CreateIndex +CREATE INDEX "EventLogs_Deposit_staker_idx" ON "EventLogs_Deposit"("staker"); diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 340a1247..5b73d023 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -11,6 +11,7 @@ datasource db { model Avs { address String @id @unique + metadataUrl String? metadataName String metadataDescription String metadataDiscord String? @@ -18,9 +19,15 @@ model Avs { metadataTelegram String? metadataWebsite String? metadataX String? + isMetadataSynced Boolean @default(false) operators AvsOperator[] curatedMetadata AvsCuratedMetadata? + + createdAtBlock BigInt @default(0) + updatedAtBlock BigInt @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } model AvsCuratedMetadata { @@ -60,6 +67,7 @@ model Strategies { model Operator { address String @id @unique + metadataUrl String? metadataName String metadataDescription String metadataDiscord String? @@ -67,10 +75,16 @@ model Operator { metadataTelegram String? metadataWebsite String? metadataX String? + isMetadataSynced Boolean @default(false) avs AvsOperator[] shares OperatorStrategyShares[] stakers Staker[] + + createdAtBlock BigInt @default(0) + updatedAtBlock BigInt @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } model OperatorStrategyShares { @@ -89,6 +103,11 @@ model Staker { operatorAddress String? shares StakerStrategyShares[] + + createdAtBlock BigInt @default(0) + updatedAtBlock BigInt @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } model StakerStrategyShares { @@ -100,10 +119,26 @@ model StakerStrategyShares { @@id([stakerAddress, strategyAddress]) } +model Deposit { + transactionHash String @id @unique + stakerAddress String + tokenAddress String + strategyAddress String + shares String + + createdAtBlock BigInt @default(0) + createdAt DateTime @default(now()) +} + model Pod { address String @id @unique owner String - blockNumber BigInt + blockNumber BigInt // @Deprecated, will remove in future release + + createdAtBlock BigInt @default(0) + updatedAtBlock BigInt @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } model ValidatorRestake { @@ -136,8 +171,10 @@ model Withdrawal { shares String[] startBlock BigInt - createdAtBlock BigInt - updatedAtBlock BigInt + createdAtBlock BigInt @default(0) + updatedAtBlock BigInt @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } // Collection to store system settings @@ -147,3 +184,200 @@ model Settings { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +// RAW Event Logs + +model EventLogs_AVSMetadataURIUpdated { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + avs String + metadataURI String + + @@id([transactionHash, transactionIndex]) + @@index([avs]) +} + +model EventLogs_OperatorMetadataURIUpdated { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + operator String + metadataURI String + + @@id([transactionHash, transactionIndex]) + @@index([operator]) +} + +model EventLogs_OperatorAVSRegistrationStatusUpdated { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + operator String + avs String + status Int + + @@id([transactionHash, transactionIndex]) + @@index([operator, avs]) +} + +model EventLogs_PodDeployed { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + eigenPod String + podOwner String + + @@id([transactionHash, transactionIndex]) + @@index([eigenPod, podOwner]) +} + +model EventLogs_StakerDelegated { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + staker String + operator String + + @@id([transactionHash, transactionIndex]) + @@index([staker, operator]) +} + +model EventLogs_StakerUndelegated { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + staker String + operator String + + @@id([transactionHash, transactionIndex]) + @@index([staker, operator]) +} + +model EventLogs_OperatorSharesIncreased { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + staker String + operator String + strategy String + shares String + + @@id([transactionHash, transactionIndex]) + @@index([staker, operator]) +} + +model EventLogs_OperatorSharesDecreased { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + staker String + operator String + strategy String + shares String + + @@id([transactionHash, transactionIndex]) + @@index([staker, operator]) +} + +model EventLogs_WithdrawalQueued { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + withdrawalRoot String + staker String + delegatedTo String + withdrawer String + nonce BigInt + startBlock BigInt + strategies String[] + shares String[] + + @@id([transactionHash, transactionIndex]) + @@index([withdrawalRoot]) +} + +model EventLogs_WithdrawalCompleted { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + withdrawalRoot String + + @@id([transactionHash, transactionIndex]) + @@index([withdrawalRoot]) +} + +model EventLogs_Deposit { + address String + + transactionHash String + transactionIndex Int + blockNumber BigInt + blockHash String + blockTime DateTime + + staker String + token String + strategy String + shares String + + @@id([transactionHash, transactionIndex]) + @@index([staker]) +} + +// Misc + +model Evm_BlockData { + number BigInt @id + timestamp DateTime +} diff --git a/packages/seeder/src/blocks/seedBlockData.ts b/packages/seeder/src/blocks/seedBlockData.ts new file mode 100644 index 00000000..1f3d66f9 --- /dev/null +++ b/packages/seeder/src/blocks/seedBlockData.ts @@ -0,0 +1,63 @@ +import prisma from '@prisma/client' +import { getPrismaClient } from '../utils/prismaClient' +import { getViemClient } from '../utils/viemClient' +import { + baseBlock, + bulkUpdateDbTransactions, + loopThroughBlocks +} from '../utils/seeder' + +export async function seedBlockData(toBlock?: bigint, fromBlock?: bigint) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const lastKnownBlock = await prismaClient.evm_BlockData.findFirst({ + orderBy: { number: 'desc' } + }) + + const firstBlock = fromBlock + ? fromBlock + : lastKnownBlock + ? lastKnownBlock.number + : baseBlock + + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Retrieve blocks in batches and extract timestamps + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + // biome-ignore lint/suspicious/noExplicitAny: + const promises: any[] = [] + for (let blockNumber = fromBlock; blockNumber <= toBlock; blockNumber++) { + promises.push(viemClient.getBlock({ blockNumber: blockNumber })) + } + + const blocks = await Promise.all(promises) + const newBlockData: prisma.Evm_BlockData[] = [] + + for (const block of blocks) { + const timestamp = new Date(Number(block.timestamp) * 1000) + + newBlockData.push({ + number: block.number, + timestamp + }) + } + + await bulkUpdateDbTransactions( + [ + prismaClient.evm_BlockData.createMany({ + data: newBlockData, + skipDuplicates: true + }) + ], + `[Meta] Block data from: ${fromBlock} to: ${toBlock} size: ${Number( + toBlock - fromBlock + )}` + ) + }, + 99n + ) +} diff --git a/packages/seeder/src/events/seedLogsAVSMetadata.ts b/packages/seeder/src/events/seedLogsAVSMetadata.ts new file mode 100644 index 00000000..c77285d3 --- /dev/null +++ b/packages/seeder/src/events/seedLogsAVSMetadata.ts @@ -0,0 +1,93 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_avs' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsAVSMetadata( + toBlock?: bigint, + fromBlock?: bigint +) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + const logsAVSMetadataURIUpdated: prisma.EventLogs_AVSMetadataURIUpdated[] = + [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().AVSDirectory, + event: parseAbiItem( + 'event AVSMetadataURIUpdated(address indexed avs, string metadataURI)' + ), + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + logsAVSMetadataURIUpdated.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + avs: String(log.args.avs), + metadataURI: String(log.args.metadataURI) + }) + } + + dbTransactions.push( + prismaClient.eventLogs_AVSMetadataURIUpdated.createMany({ + data: logsAVSMetadataURIUpdated, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = logsAVSMetadataURIUpdated.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] AVS Metadata from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/events/seedLogsDeposit.ts b/packages/seeder/src/events/seedLogsDeposit.ts new file mode 100644 index 00000000..9aabd6a2 --- /dev/null +++ b/packages/seeder/src/events/seedLogsDeposit.ts @@ -0,0 +1,91 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_deposit' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsDeposit(toBlock?: bigint, fromBlock?: bigint) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + const logsDeposit: prisma.EventLogs_Deposit[] = [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().StrategyManager, + event: parseAbiItem( + 'event Deposit(address staker, address token, address strategy, uint256 shares)' + ), + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + logsDeposit.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + staker: String(log.args.staker), + token: String(log.args.token), + strategy: String(log.args.strategy), + shares: String(log.args.shares) + }) + } + + dbTransactions.push( + prismaClient.eventLogs_Deposit.createMany({ + data: logsDeposit, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = logsDeposit.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] Deposit from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/events/seedLogsOperatorAVSRegistrationStatus.ts b/packages/seeder/src/events/seedLogsOperatorAVSRegistrationStatus.ts new file mode 100644 index 00000000..9fae1efa --- /dev/null +++ b/packages/seeder/src/events/seedLogsOperatorAVSRegistrationStatus.ts @@ -0,0 +1,95 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_avsOperators' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsOperatorAVSRegistrationStatus( + toBlock?: bigint, + fromBlock?: bigint +) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + const logsOperatorAVSRegistrationStatusUpdated: prisma.EventLogs_OperatorAVSRegistrationStatusUpdated[] = + [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().AVSDirectory, + event: parseAbiItem( + 'event OperatorAVSRegistrationStatusUpdated(address indexed operator, address indexed avs, uint8 status)' + ), + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + logsOperatorAVSRegistrationStatusUpdated.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + operator: String(log.args.operator), + avs: String(log.args.avs), + status: Number(log.args.status) + }) + } + + dbTransactions.push( + prismaClient.eventLogs_OperatorAVSRegistrationStatusUpdated.createMany({ + data: logsOperatorAVSRegistrationStatusUpdated, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = logsOperatorAVSRegistrationStatusUpdated.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] Operator Registration from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/events/seedLogsOperatorMetadata.ts b/packages/seeder/src/events/seedLogsOperatorMetadata.ts new file mode 100644 index 00000000..57dab343 --- /dev/null +++ b/packages/seeder/src/events/seedLogsOperatorMetadata.ts @@ -0,0 +1,93 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_operators' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsOperatorMetadata( + toBlock?: bigint, + fromBlock?: bigint +) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + const logsOperatorMetadataURIUpdated: prisma.EventLogs_OperatorMetadataURIUpdated[] = + [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().DelegationManager, + event: parseAbiItem( + 'event OperatorMetadataURIUpdated(address indexed operator, string metadataURI)' + ), + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + logsOperatorMetadataURIUpdated.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + operator: String(log.args.operator), + metadataURI: String(log.args.metadataURI) + }) + } + + dbTransactions.push( + prismaClient.eventLogs_OperatorMetadataURIUpdated.createMany({ + data: logsOperatorMetadataURIUpdated, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = logsOperatorMetadataURIUpdated.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] Operator Metadata from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/events/seedLogsOperatorShares.ts b/packages/seeder/src/events/seedLogsOperatorShares.ts new file mode 100644 index 00000000..ab281131 --- /dev/null +++ b/packages/seeder/src/events/seedLogsOperatorShares.ts @@ -0,0 +1,125 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_operatorShares' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsOperatorShares( + toBlock?: bigint, + fromBlock?: bigint +) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + const logsOperatorSharesIncreased: prisma.EventLogs_OperatorSharesIncreased[] = + [] + const logsOperatorSharesDecreased: prisma.EventLogs_OperatorSharesDecreased[] = + [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().DelegationManager, + events: [ + parseAbiItem( + 'event OperatorSharesIncreased(address indexed operator, address staker, address strategy, uint256 shares)' + ), + parseAbiItem( + 'event OperatorSharesDecreased(address indexed operator, address staker, address strategy, uint256 shares)' + ) + ], + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + if (log.eventName === 'OperatorSharesIncreased') { + logsOperatorSharesIncreased.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + operator: String(log.args.operator), + staker: String(log.args.staker), + strategy: String(log.args.strategy), + shares: String(log.args.shares) + }) + } else if (log.eventName === 'OperatorSharesDecreased') { + logsOperatorSharesDecreased.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + operator: String(log.args.operator), + staker: String(log.args.staker), + strategy: String(log.args.strategy), + shares: String(log.args.shares) + }) + } + } + + dbTransactions.push( + prismaClient.eventLogs_OperatorSharesIncreased.createMany({ + data: logsOperatorSharesIncreased, + skipDuplicates: true + }) + ) + + dbTransactions.push( + prismaClient.eventLogs_OperatorSharesDecreased.createMany({ + data: logsOperatorSharesDecreased, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = + logsOperatorSharesDecreased.length + logsOperatorSharesIncreased.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] Operator Shares from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/events/seedLogsPodDeployed.ts b/packages/seeder/src/events/seedLogsPodDeployed.ts new file mode 100644 index 00000000..dee25cfc --- /dev/null +++ b/packages/seeder/src/events/seedLogsPodDeployed.ts @@ -0,0 +1,93 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_pods' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsPodDeployed( + toBlock?: bigint, + fromBlock?: bigint +) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + const logsPodDeployed: prisma.EventLogs_PodDeployed[] = [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().EigenPodManager, + event: parseAbiItem( + 'event PodDeployed(address indexed eigenPod, address indexed podOwner)' + ), + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + logsPodDeployed.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + eigenPod: String(log.args.eigenPod), + podOwner: String(log.args.podOwner) + }) + } + + dbTransactions.push( + prismaClient.eventLogs_PodDeployed.createMany({ + data: logsPodDeployed, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = logsPodDeployed.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] Pod Deployed from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/events/seedLogsStakerDelegation.ts b/packages/seeder/src/events/seedLogsStakerDelegation.ts new file mode 100644 index 00000000..48368e36 --- /dev/null +++ b/packages/seeder/src/events/seedLogsStakerDelegation.ts @@ -0,0 +1,120 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_stakers' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsStakerDelegation( + toBlock?: bigint, + fromBlock?: bigint +) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + const logsStakerDelegated: prisma.EventLogs_StakerDelegated[] = [] + const logsStakerUndelegated: prisma.EventLogs_StakerUndelegated[] = [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().DelegationManager, + events: [ + parseAbiItem( + 'event StakerDelegated(address indexed staker, address indexed operator)' + ), + parseAbiItem( + 'event StakerUndelegated(address indexed staker, address indexed operator)' + ) + ], + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + if (log.eventName === 'StakerDelegated') { + logsStakerDelegated.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + staker: String(log.args.staker), + operator: String(log.args.operator) + }) + } else if (log.eventName === 'StakerUndelegated') { + logsStakerUndelegated.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + staker: String(log.args.staker), + operator: String(log.args.operator) + }) + } + } + + dbTransactions.push( + prismaClient.eventLogs_StakerDelegated.createMany({ + data: logsStakerDelegated, + skipDuplicates: true + }) + ) + + dbTransactions.push( + prismaClient.eventLogs_StakerUndelegated.createMany({ + data: logsStakerUndelegated, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = + logsStakerDelegated.length + logsStakerUndelegated.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] Staker Delegation from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/events/seedLogsWithdrawalCompleted.ts b/packages/seeder/src/events/seedLogsWithdrawalCompleted.ts new file mode 100644 index 00000000..e0e74dca --- /dev/null +++ b/packages/seeder/src/events/seedLogsWithdrawalCompleted.ts @@ -0,0 +1,92 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_completedWithdrawals' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsWithdrawalCompleted( + toBlock?: bigint, + fromBlock?: bigint +) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + const logsWithdrawalCompleted: prisma.EventLogs_WithdrawalCompleted[] = [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().DelegationManager, + event: parseAbiItem([ + 'event WithdrawalCompleted(bytes32 withdrawalRoot)' + ]), + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + logsWithdrawalCompleted.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + withdrawalRoot: String(log.args.withdrawalRoot) + }) + } + + dbTransactions.push( + prismaClient.eventLogs_WithdrawalCompleted.createMany({ + data: logsWithdrawalCompleted, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = logsWithdrawalCompleted.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] Withdrawal Completed from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/events/seedLogsWithdrawalQueued.ts b/packages/seeder/src/events/seedLogsWithdrawalQueued.ts new file mode 100644 index 00000000..391c582b --- /dev/null +++ b/packages/seeder/src/events/seedLogsWithdrawalQueued.ts @@ -0,0 +1,100 @@ +import prisma from '@prisma/client' +import { parseAbiItem } from 'viem' +import { getEigenContracts } from '../data/address' +import { getViemClient } from '../utils/viemClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + getBlockDataFromDb, + loopThroughBlocks +} from '../utils/seeder' +import { getPrismaClient } from '../utils/prismaClient' + +const blockSyncKeyLogs = 'lastSyncedBlock_logs_queuedWithdrawals' + +/** + * Utility function to seed event logs + * + * @param fromBlock + * @param toBlock + */ +export async function seedLogsWithdrawalQueued( + toBlock?: bigint, + fromBlock?: bigint +) { + const viemClient = getViemClient() + const prismaClient = getPrismaClient() + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + + // Loop through evm logs + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const blockData = await getBlockDataFromDb(fromBlock, toBlock) + + try { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + const logsWithdrawalQueued: prisma.EventLogs_WithdrawalQueued[] = [] + + const logs = await viemClient.getLogs({ + address: getEigenContracts().DelegationManager, + event: parseAbiItem([ + 'event WithdrawalQueued(bytes32 withdrawalRoot, Withdrawal withdrawal)', + 'struct Withdrawal { address staker; address delegatedTo; address withdrawer; uint256 nonce; uint32 startBlock; address[] strategies; uint256[] shares; }' + ]), + fromBlock, + toBlock + }) + + // Setup a list containing event data + for (const l in logs) { + const log = logs[l] + + logsWithdrawalQueued.push({ + address: log.address, + transactionHash: log.transactionHash, + transactionIndex: log.logIndex, + blockNumber: BigInt(log.blockNumber), + blockHash: log.blockHash, + blockTime: blockData.get(log.blockNumber) || new Date(0), + withdrawalRoot: String(log.args.withdrawalRoot), + staker: String(log.args.withdrawal?.staker), + delegatedTo: String(log.args.withdrawal?.delegatedTo), + withdrawer: String(log.args.withdrawal?.withdrawer), + nonce: log.args.withdrawal?.nonce || 0n, + startBlock: BigInt(log.args.withdrawal?.startBlock || 0), + strategies: (log.args.withdrawal?.strategies as string[]) || [], + shares: log.args.withdrawal?.shares.map((s) => s.toString()) || [] + }) + } + + dbTransactions.push( + prismaClient.eventLogs_WithdrawalQueued.createMany({ + data: logsWithdrawalQueued, + skipDuplicates: true + }) + ) + + // Store last synced block + dbTransactions.push( + prismaClient.settings.upsert({ + where: { key: blockSyncKeyLogs }, + update: { value: Number(toBlock) }, + create: { key: blockSyncKeyLogs, value: Number(toBlock) } + }) + ) + + // Update database + const seedLength = logsWithdrawalQueued.length + + await bulkUpdateDbTransactions( + dbTransactions, + `[Logs] Withdrawal Queued from: ${fromBlock} to: ${toBlock} size: ${seedLength}` + ) + } catch (error) {} + }) +} diff --git a/packages/seeder/src/index.ts b/packages/seeder/src/index.ts index 8158e9da..ff784af7 100644 --- a/packages/seeder/src/index.ts +++ b/packages/seeder/src/index.ts @@ -6,12 +6,25 @@ import { seedOperators } from './seedOperators' import { seedPods } from './seedPods' import { seedStakers } from './seedStakers' import { getViemClient } from './utils/viemClient' +import { seedBlockData } from './blocks/seedBlockData' +import { seedLogsAVSMetadata } from './events/seedLogsAVSMetadata' +import { seedLogsOperatorMetadata } from './events/seedLogsOperatorMetadata' +import { seedLogsOperatorAVSRegistrationStatus } from './events/seedLogsOperatorAVSRegistrationStatus' +import { seedLogsOperatorShares } from './events/seedLogsOperatorShares' +import { seedLogsStakerDelegation } from './events/seedLogsStakerDelegation' +import { seedLogsPodDeployed } from './events/seedLogsPodDeployed' import { seedOperatorShares } from './seedOperatorShares' import { seedValidators } from './seedValidators' import { seedQueuedWithdrawals } from './seedWithdrawalsQueued' import { seedCompletedWithdrawals } from './seedWithdrawalsCompleted' +import { seedLogsWithdrawalQueued } from './events/seedLogsWithdrawalQueued' +import { seedLogsWithdrawalCompleted } from './events/seedLogsWithdrawalCompleted' +import { seedLogsDeposit } from './events/seedLogsDeposit' +import { seedDeposits } from './seedDeposits' +import { monitorAvsMetadata } from './monitors/avsMetadata' +import { monitorOperatorMetadata } from './monitors/operatorMetadata' -console.log('Initializing seeder ...') +console.log('Initializing Seeder ...') function delay(seconds: number) { return new Promise((resolve) => setTimeout(resolve, seconds * 1000)) @@ -22,41 +35,71 @@ async function seedEigenDataLoop() { try { const viemClient = getViemClient() const targetBlock = await viemClient.getBlockNumber() - console.log('Seeding Eigen Data ...', targetBlock) - - await seedAvs(targetBlock) - await seedOperators(targetBlock) - await seedAvsOperators(targetBlock) - await seedStakers(targetBlock) - await seedOperatorShares(targetBlock) - await seedQueuedWithdrawals(targetBlock) - await seedCompletedWithdrawals(targetBlock) + console.log('\nSeeding data ...', targetBlock) + + await seedBlockData(targetBlock) + await seedLogsAVSMetadata(targetBlock) + await seedLogsOperatorMetadata(targetBlock) + await seedLogsOperatorAVSRegistrationStatus(targetBlock) + await seedLogsOperatorShares(targetBlock) + await seedLogsStakerDelegation(targetBlock) + await seedLogsPodDeployed(targetBlock) + await seedLogsWithdrawalQueued(targetBlock) + await seedLogsWithdrawalCompleted(targetBlock) + await seedLogsDeposit(targetBlock) + + await seedAvs() + await seedOperators() + await seedAvsOperators() + await seedStakers() + await seedOperatorShares() + await seedQueuedWithdrawals() + await seedCompletedWithdrawals() + await seedDeposits() + await seedPods() } catch (error) { - console.log('Failed to seed AVS and Opeartors at:', Date.now()) + console.log('Failed to seed data at:', Date.now()) + console.log(error) } - await delay(120) // Wait for 2 minutes (120 seconds) + await delay(60) } } -async function seedEigenPodValidators() { +async function monitorMetadata() { await delay(60) while (true) { try { - const viemClient = getViemClient() - const targetBlock = await viemClient.getBlockNumber() - console.log('Seeding Eigen Pods Data ...', targetBlock) + console.log('\nMonitoring metadata...') + + await monitorAvsMetadata() + await monitorOperatorMetadata() + } catch (error) { + console.log('Failed to monitor metadata at:', Date.now()) + } + + await delay(120) + } +} + +async function seedEigenPodValidators() { + await delay(120) + + while (true) { + try { + console.log('\nSeeding Eigen Pods data ...') - await seedPods(targetBlock) await seedValidators() } catch (error) { - console.log('Failed to seed validators at block:', Date.now()) + console.log(error) + console.log('Failed to seed Validators at:', Date.now()) } - await delay(600) // Wait for 10 minutes (600 seconds) + await delay(3600) } } seedEigenDataLoop() +monitorMetadata() seedEigenPodValidators() diff --git a/packages/seeder/src/monitors/avsMetadata.ts b/packages/seeder/src/monitors/avsMetadata.ts new file mode 100644 index 00000000..746442e8 --- /dev/null +++ b/packages/seeder/src/monitors/avsMetadata.ts @@ -0,0 +1,72 @@ +import { getPrismaClient } from '../utils/prismaClient' +import { fetchWithTimeout, bulkUpdateDbTransactions } from '../utils/seeder' +import { isValidMetadataUrl, validateMetadata } from '../utils/metadata' + +export async function monitorAvsMetadata() { + const prismaClient = getPrismaClient() + + let skip = 0 + const take = 100 + + while (true) { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + const avsEntries = await prismaClient.avs.findMany({ + where: { + isMetadataSynced: false + }, + take: take, + skip: skip, + orderBy: { + createdAtBlock: 'asc' + } + }) + + if (avsEntries.length === 0) { + break + } + + for (const record of avsEntries) { + try { + if (record.metadataUrl && isValidMetadataUrl(record.metadataUrl)) { + const response = await fetchWithTimeout(record.metadataUrl) + const data = response ? await response.text() : '' + const avsMetadata = validateMetadata(data) + + if (avsMetadata) { + dbTransactions.push( + prismaClient.avs.update({ + where: { address: record.address }, + data: { + metadataName: avsMetadata.name, + metadataDescription: avsMetadata.description, + metadataLogo: avsMetadata.logo, + metadataDiscord: avsMetadata.discord, + metadataTelegram: avsMetadata.telegram, + metadataWebsite: avsMetadata.website, + metadataX: avsMetadata.x, + isMetadataSynced: true + } + }) + ) + } else { + throw new Error('Invalid avs metadata uri') + } + } else { + throw new Error('Invalid avs metadata uri') + } + } catch (error) {} + } + + if (dbTransactions.length > 0) { + await bulkUpdateDbTransactions( + dbTransactions, + `[Monitor] Updated AVS metadatas: ${dbTransactions.length}` + ) + } + skip += take + } + + console.log('[Monitor] All AVS metadatas up-to-date') +} diff --git a/packages/seeder/src/monitors/operatorMetadata.ts b/packages/seeder/src/monitors/operatorMetadata.ts new file mode 100644 index 00000000..15e668f3 --- /dev/null +++ b/packages/seeder/src/monitors/operatorMetadata.ts @@ -0,0 +1,72 @@ +import { getPrismaClient } from '../utils/prismaClient' +import { fetchWithTimeout, bulkUpdateDbTransactions } from '../utils/seeder' +import { isValidMetadataUrl, validateMetadata } from '../utils/metadata' + +export async function monitorOperatorMetadata() { + const prismaClient = getPrismaClient() + + let skip = 0 + const take = 100 + + while (true) { + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + const operatorEntries = await prismaClient.operator.findMany({ + where: { + isMetadataSynced: false + }, + take: take, + skip: skip, + orderBy: { + createdAtBlock: 'asc' + } + }) + + if (operatorEntries.length === 0) { + break + } + + for (const record of operatorEntries) { + try { + if (record.metadataUrl && isValidMetadataUrl(record.metadataUrl)) { + const response = await fetchWithTimeout(record.metadataUrl) + const data = response ? await response.text() : '' + const operatorMetadata = validateMetadata(data) + + if (operatorMetadata) { + dbTransactions.push( + prismaClient.operator.update({ + where: { address: record.address }, + data: { + metadataName: operatorMetadata.name, + metadataDescription: operatorMetadata.description, + metadataLogo: operatorMetadata.logo, + metadataDiscord: operatorMetadata.discord, + metadataTelegram: operatorMetadata.telegram, + metadataWebsite: operatorMetadata.website, + metadataX: operatorMetadata.x, + isMetadataSynced: true + } + }) + ) + } else { + throw new Error('Invalid operator metadata uri') + } + } else { + throw new Error('Invalid operator metadata uri') + } + } catch (error) {} + } + + if (dbTransactions.length > 0) { + await bulkUpdateDbTransactions( + dbTransactions, + `[Monitor] Updated Operator metadatas: ${dbTransactions.length}` + ) + } + skip += take + } + + console.log('[Monitor] All Operator metadatas up-to-date') +} diff --git a/packages/seeder/src/seedAvs.ts b/packages/seeder/src/seedAvs.ts index 8f3e8875..a74b65e4 100644 --- a/packages/seeder/src/seedAvs.ts +++ b/packages/seeder/src/seedAvs.ts @@ -1,8 +1,5 @@ -import { parseAbiItem } from 'viem' -import { isValidMetadataUrl, validateMetadata } from './utils/metadata' -import type { EntityMetadata } from './utils/metadata' -import { getEigenContracts } from './data/address' -import { getViemClient } from './utils/viemClient' +import type prisma from '@prisma/client' +import { type EntityMetadata, defaultMetadata } from './utils/metadata' import { getPrismaClient } from './utils/prismaClient' import { baseBlock, @@ -13,87 +10,112 @@ import { } from './utils/seeder' const blockSyncKey = 'lastSyncedBlock_avs' +const blockSyncKeyLogs = 'lastSyncedBlock_logs_avs' + +interface AvsEntryRecord { + metadataUrl: string + metadata: EntityMetadata + isMetadataSynced: boolean + createdAtBlock: bigint + updatedAtBlock: bigint + createdAt: Date + updatedAt: Date +} -/** - * Utility function to seed avs - * - * @param fromBlock - * @param toBlock - */ export async function seedAvs(toBlock?: bigint, fromBlock?: bigint) { - console.log('Seeding AVS ...') - - const viemClient = getViemClient() const prismaClient = getPrismaClient() - const avsList: Map = new Map() + const avsList: Map = new Map() const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) - const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() - - // Loop through evm logs - await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { - const logs = await viemClient.getLogs({ - address: getEigenContracts().AVSDirectory, - event: parseAbiItem( - 'event AVSMetadataURIUpdated(address indexed avs, string metadataURI)' - ), - fromBlock, - toBlock - }) - - for (const l in logs) { - const log = logs[l] - - const avsAddress = String(log.args.avs).toLowerCase() - avsList.set(avsAddress, { - name: '', - website: '', - description: '', - logo: '', - x: '', - discord: '', - telegram: '' - }) + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) - if (log.args.metadataURI && isValidMetadataUrl(log.args.metadataURI)) { - const response = await fetch(log.args.metadataURI) - const data = await response.text() - const avsMetadata = validateMetadata(data) + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log( + `[In Sync] [Data] AVS MetadataURI from: ${firstBlock} to: ${lastBlock}` + ) + return + } - if (avsMetadata) { - avsList.set(avsAddress, avsMetadata) + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + const logs = await prismaClient.eventLogs_AVSMetadataURIUpdated.findMany({ + where: { + blockNumber: { + gt: fromBlock, + lte: toBlock + } } - } - } + }) - console.log( - `Avs registered between blocks ${fromBlock} ${toBlock}: ${logs.length}` - ) - }) + for (const l in logs) { + const log = logs[l] + + const avsAddress = String(log.avs).toLowerCase() + const existingRecord = avsList.get(avsAddress) + + const blockNumber = BigInt(log.blockNumber) + const timestamp = log.blockTime + + if (existingRecord) { + // Avs has been registered before in this fetch + avsList.set(avsAddress, { + metadataUrl: log.metadataURI, + metadata: defaultMetadata, + isMetadataSynced: false, + createdAtBlock: existingRecord.createdAtBlock, + updatedAtBlock: blockNumber, + createdAt: existingRecord.createdAt, + updatedAt: timestamp + }) + } else { + // Avs being registered for the first time in this fetch + avsList.set(avsAddress, { + metadataUrl: log.metadataURI, + metadata: defaultMetadata, + isMetadataSynced: false, + createdAtBlock: blockNumber, + updatedAtBlock: blockNumber, + createdAt: timestamp, // Will be omitted in upsert if avs exists in db + updatedAt: timestamp + }) + } + } + }, + 10_000n + ) // Prepare db transaction object // biome-ignore lint/suspicious/noExplicitAny: const dbTransactions: any[] = [] if (firstBlock === baseBlock) { + dbTransactions.push(prismaClient.avsOperator.deleteMany()) dbTransactions.push(prismaClient.avs.deleteMany()) - const newAvs: { - address: string - metadataName: string - metadataDescription: string - metadataDiscord?: string | null - metadataLogo?: string | null - metadataTelegram?: string | null - metadataWebsite?: string | null - metadataX?: string | null - }[] = [] - - for (const [address, metadata] of avsList) { + const newAvs: prisma.Avs[] = [] + + for (const [ + address, + { + metadataUrl, + metadata, + isMetadataSynced, + createdAtBlock, + updatedAtBlock, + createdAt, + updatedAt + } + ] of avsList) { newAvs.push({ address, + metadataUrl, metadataName: metadata.name, metadataDescription: metadata.description, metadataLogo: metadata.logo, @@ -101,6 +123,11 @@ export async function seedAvs(toBlock?: bigint, fromBlock?: bigint) { metadataTelegram: metadata.telegram, metadataWebsite: metadata.website, metadataX: metadata.x, + isMetadataSynced, + createdAtBlock, + updatedAtBlock, + createdAt, + updatedAt }) } @@ -111,21 +138,37 @@ export async function seedAvs(toBlock?: bigint, fromBlock?: bigint) { }) ) } else { - for (const [address, metadata] of avsList) { + for (const [ + address, + { + metadataUrl, + metadata, + isMetadataSynced, + createdAtBlock, + updatedAtBlock, + createdAt, + updatedAt + } + ] of avsList) { dbTransactions.push( prismaClient.avs.upsert({ where: { address }, update: { + metadataUrl, metadataName: metadata.name, metadataDescription: metadata.description, metadataLogo: metadata.logo, metadataDiscord: metadata.discord, metadataTelegram: metadata.telegram, metadataWebsite: metadata.website, - metadataX: metadata.x + metadataX: metadata.x, + isMetadataSynced, + updatedAtBlock, + updatedAt }, create: { address, + metadataUrl, metadataName: metadata.name, metadataDescription: metadata.description, metadataLogo: metadata.logo, @@ -133,16 +176,22 @@ export async function seedAvs(toBlock?: bigint, fromBlock?: bigint) { metadataTelegram: metadata.telegram, metadataWebsite: metadata.website, metadataX: metadata.x, + isMetadataSynced, + createdAtBlock, + updatedAtBlock, + createdAt, + updatedAt } }) ) } } - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] AVS MetadataURI from: ${firstBlock} to: ${lastBlock} size: ${avsList.size}` + ) - // Storing last sycned block + // Storing last synced block await saveLastSyncBlock(blockSyncKey, lastBlock) - - console.log('Seeded AVS:', avsList.size) } diff --git a/packages/seeder/src/seedAvsOperators.ts b/packages/seeder/src/seedAvsOperators.ts index b6a5b26e..af0deb1f 100644 --- a/packages/seeder/src/seedAvsOperators.ts +++ b/packages/seeder/src/seedAvsOperators.ts @@ -1,6 +1,3 @@ -import { parseAbiItem } from 'viem' -import { getViemClient } from './utils/viemClient' -import { getEigenContracts } from './data/address' import { getPrismaClient } from './utils/prismaClient' import { baseBlock, @@ -11,18 +8,26 @@ import { } from './utils/seeder' const blockSyncKey = 'lastSyncedBlock_avsOperators' +const blockSyncKeyLogs = 'lastSyncedBlock_logs_avsOperators' export async function seedAvsOperators(toBlock?: bigint, fromBlock?: bigint) { - console.log('Seeding AVS Operators ...') - - const viemClient = getViemClient() const prismaClient = getPrismaClient() const avsOperatorsList: Map> = new Map() const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) - const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log( + `[In Sync] [Data] AVS Operators from: ${firstBlock} to: ${lastBlock}` + ) + return + } // Load initial operator staker state const avs = await prismaClient.avs.findMany({ @@ -31,34 +36,37 @@ export async function seedAvsOperators(toBlock?: bigint, fromBlock?: bigint) { avs.map((a) => avsOperatorsList.set(a.address, new Map())) - // Loop through evm logs - await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { - const logs = await viemClient.getLogs({ - address: getEigenContracts().AVSDirectory, - event: parseAbiItem( - 'event OperatorAVSRegistrationStatusUpdated(address indexed operator, address indexed avs, uint8 status)' - ), - fromBlock, - toBlock - }) - - for (const l in logs) { - const log = logs[l] - - const avsAddress = String(log.args.avs).toLowerCase() - const operatorAddress = String(log.args.operator).toLowerCase() - - if (avsOperatorsList.has(avsAddress)) { - avsOperatorsList - .get(avsAddress) - ?.set(operatorAddress, log.args.status || 0) - } - } + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + const logs = + await prismaClient.eventLogs_OperatorAVSRegistrationStatusUpdated.findMany( + { + where: { + blockNumber: { + gt: fromBlock, + lte: toBlock + } + } + } + ) - console.log( - `Avs operators updated between blocks ${fromBlock} ${toBlock}: ${logs.length}` - ) - }) + for (const l in logs) { + const log = logs[l] + + const avsAddress = String(log.avs).toLowerCase() + const operatorAddress = String(log.operator).toLowerCase() + + if (avsOperatorsList.has(avsAddress)) { + avsOperatorsList + .get(avsAddress) + ?.set(operatorAddress, log.status || 0) + } + } + }, + 10_000n + ) // Prepare db transaction object // biome-ignore lint/suspicious/noExplicitAny: @@ -111,10 +119,11 @@ export async function seedAvsOperators(toBlock?: bigint, fromBlock?: bigint) { } } - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] AVS Operator from: ${firstBlock} to: ${lastBlock} size: ${avsOperatorsList.size}` + ) // Storing last sycned block await saveLastSyncBlock(blockSyncKey, lastBlock) - - console.log('Seeded AVS Operators:', avsOperatorsList.size) } diff --git a/packages/seeder/src/seedDeposits.ts b/packages/seeder/src/seedDeposits.ts new file mode 100644 index 00000000..c877104a --- /dev/null +++ b/packages/seeder/src/seedDeposits.ts @@ -0,0 +1,78 @@ +import prisma from '@prisma/client' +import { getPrismaClient } from './utils/prismaClient' +import { + bulkUpdateDbTransactions, + fetchLastSyncBlock, + loopThroughBlocks, + saveLastSyncBlock +} from './utils/seeder' + +const blockSyncKey = 'lastSyncedBlock_deposit' +const blockSyncKeyLogs = 'lastSyncedBlock_logs_deposit' + +export async function seedDeposits(toBlock?: bigint, fromBlock?: bigint) { + const prismaClient = getPrismaClient() + const depositList: prisma.Deposit[] = [] + + const firstBlock = fromBlock + ? fromBlock + : await fetchLastSyncBlock(blockSyncKey) + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log(`[In Sync] [Data] Deposit from: ${firstBlock} to: ${lastBlock}`) + return + } + + await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { + const logs = await prismaClient.eventLogs_Deposit.findMany({ + where: { + blockNumber: { + gt: fromBlock, + lte: toBlock + } + } + }) + + for (const l in logs) { + const log = logs[l] + + const blockNumber = BigInt(log.blockNumber) + const timestamp = log.blockTime + + depositList.push({ + transactionHash: log.transactionHash.toLowerCase(), + stakerAddress: log.staker.toLowerCase(), + tokenAddress: log.token.toLowerCase(), + strategyAddress: log.strategy.toLowerCase(), + shares: log.shares, + createdAtBlock: blockNumber, + createdAt: timestamp + }) + } + }) + + // Prepare db transaction object + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + if (depositList.length > 0) { + dbTransactions.push( + prismaClient.deposit.createMany({ + data: depositList, + skipDuplicates: true + }) + ) + } + + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] Deposit from: ${firstBlock} to: ${lastBlock} size: ${depositList.length}` + ) + + // Storing last synced block + await saveLastSyncBlock(blockSyncKey, lastBlock) +} diff --git a/packages/seeder/src/seedOperatorShares.ts b/packages/seeder/src/seedOperatorShares.ts index 1b2de63e..5c6808af 100644 --- a/packages/seeder/src/seedOperatorShares.ts +++ b/packages/seeder/src/seedOperatorShares.ts @@ -1,22 +1,17 @@ -import { parseAbiItem } from 'viem' -import { getEigenContracts } from './data/address' -import { getViemClient } from './utils/viemClient' import { getPrismaClient } from './utils/prismaClient' import { baseBlock, bulkUpdateDbTransactions, fetchLastSyncBlock, - IMap, + type IMap, loopThroughBlocks, saveLastSyncBlock } from './utils/seeder' const blockSyncKey = 'lastSyncedBlock_operatorShares' +const blockSyncKeyLogs = 'lastSyncedBlock_logs_operatorShares' export async function seedOperatorShares(toBlock?: bigint, fromBlock?: bigint) { - console.log('Seeding operator shares ...') - - const viemClient = getViemClient() const prismaClient = getPrismaClient() const operatorShares: IMap< string, @@ -26,99 +21,137 @@ export async function seedOperatorShares(toBlock?: bigint, fromBlock?: bigint) { const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) - const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log( + `[In Sync] [Data] Operator Shares from: ${firstBlock} to: ${lastBlock}` + ) + return + } if (firstBlock === baseBlock) { await prismaClient.operatorStrategyShares.deleteMany() } - // Loop through evm logs - await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { - const logs = await viemClient.getLogs({ - address: getEigenContracts().DelegationManager, - events: [ - parseAbiItem( - 'event OperatorSharesIncreased(address indexed operator, address staker, address strategy, uint256 shares)' - ), - parseAbiItem( - 'event OperatorSharesDecreased(address indexed operator, address staker, address strategy, uint256 shares)' - ) - ], - fromBlock, - toBlock - }) - - // Operators list - const operatorAddresses = logs.map((l) => - String(l.args.operator).toLowerCase() - ) + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + // biome-ignore lint/suspicious/noExplicitAny: + let allLogs: any[] = [] + + await prismaClient.eventLogs_OperatorSharesIncreased + .findMany({ where: { blockNumber: { gt: fromBlock, lte: toBlock } } }) + .then((logs) => { + allLogs = [ + ...allLogs, + ...logs.map((log) => ({ + ...log, + eventName: 'OperatorSharesIncreased' + })) + ] + }) - const operatorInit = await prismaClient.operator.findMany({ - where: { address: { in: operatorAddresses } }, - include: { - shares: true - } - }) + await prismaClient.eventLogs_OperatorSharesDecreased + .findMany({ where: { blockNumber: { gt: fromBlock, lte: toBlock } } }) + .then((logs) => { + allLogs = [ + ...allLogs, + ...logs.map((log) => ({ + ...log, + eventName: 'OperatorSharesDecreased' + })) + ] + }) - for (const l in logs) { - const log = logs[l] + allLogs = allLogs.sort((a, b) => { + if (a.blockNumber === b.blockNumber) { + return a.transactionIndex - b.transactionIndex + } - const operatorAddress = String(log.args.operator).toLowerCase() - const strategyAddress = String(log.args.strategy).toLowerCase() - const shares = log.args.shares - if (!shares) continue + return Number(a.blockNumber - b.blockNumber) + }) - // Load existing staker shares data - if (!operatorShares.has(operatorAddress)) { - const foundOperatorInit = operatorInit.find( - (o) => o.address.toLowerCase() === operatorAddress.toLowerCase() - ) - if (foundOperatorInit) { - operatorShares.set( - operatorAddress, - foundOperatorInit.shares.map((o) => ({ - ...o, - shares: BigInt(o.shares) - })) - ) - } else { - operatorShares.set(operatorAddress, []) - } - } + // Operators list + const operatorAddresses = allLogs.map((l) => + String(l.operator).toLowerCase() + ) + const operatorInit = + firstBlock !== baseBlock + ? await prismaClient.operator.findMany({ + where: { address: { in: operatorAddresses } }, + include: { + shares: true + } + }) + : [] - let foundSharesIndex = operatorShares - .get(operatorAddress) - .findIndex( - (os) => - os.strategyAddress.toLowerCase() === strategyAddress.toLowerCase() - ) + for (const l in allLogs) { + const log = allLogs[l] - if (foundSharesIndex !== undefined && foundSharesIndex === -1) { - operatorShares - .get(operatorAddress) - .push({ shares: 0n, strategyAddress: strategyAddress }) + const operatorAddress = String(log.operator).toLowerCase() + const strategyAddress = String(log.strategy).toLowerCase() + const shares = log.shares + if (!shares) continue + + // Load existing staker shares data + if (!operatorShares.has(operatorAddress)) { + const foundOperatorInit = operatorInit.find( + (o) => o.address.toLowerCase() === operatorAddress.toLowerCase() + ) + if (foundOperatorInit) { + operatorShares.set( + operatorAddress, + foundOperatorInit.shares.map((o) => ({ + ...o, + shares: BigInt(o.shares) + })) + ) + } else { + operatorShares.set(operatorAddress, []) + } + } - foundSharesIndex = operatorShares + let foundSharesIndex = operatorShares .get(operatorAddress) .findIndex( (os) => os.strategyAddress.toLowerCase() === strategyAddress.toLowerCase() ) - } - if (log.eventName === 'OperatorSharesIncreased') { - operatorShares.get(operatorAddress)[foundSharesIndex].shares = - operatorShares.get(operatorAddress)[foundSharesIndex].shares + shares - } else if (log.eventName === 'OperatorSharesDecreased') { - operatorShares.get(operatorAddress)[foundSharesIndex].shares = - operatorShares.get(operatorAddress)[foundSharesIndex].shares - shares + if (foundSharesIndex !== undefined && foundSharesIndex === -1) { + operatorShares + .get(operatorAddress) + .push({ shares: 0n, strategyAddress: strategyAddress }) + + foundSharesIndex = operatorShares + .get(operatorAddress) + .findIndex( + (os) => + os.strategyAddress.toLowerCase() === + strategyAddress.toLowerCase() + ) + } + + if (log.eventName === 'OperatorSharesIncreased') { + operatorShares.get(operatorAddress)[foundSharesIndex].shares = + operatorShares.get(operatorAddress)[foundSharesIndex].shares + + BigInt(shares) + } else if (log.eventName === 'OperatorSharesDecreased') { + operatorShares.get(operatorAddress)[foundSharesIndex].shares = + operatorShares.get(operatorAddress)[foundSharesIndex].shares - + BigInt(shares) + } } - } - console.log( - `Operator shares updated between blocks ${fromBlock} ${toBlock}: ${logs.length}` - ) - }) + console.log(`[Batch] Operator Shares from: ${fromBlock} to: ${toBlock}`) + }, + 10_000n + ) // biome-ignore lint/suspicious/noExplicitAny: const dbTransactions: any[] = [] @@ -174,10 +207,11 @@ export async function seedOperatorShares(toBlock?: bigint, fromBlock?: bigint) { } } - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] Operator Shares from: ${firstBlock} to: ${lastBlock} size: ${operatorShares.size}` + ) // Storing last sycned block await saveLastSyncBlock(blockSyncKey, lastBlock) - - console.log('Seeded operator shares:', operatorShares.size) } diff --git a/packages/seeder/src/seedOperators.ts b/packages/seeder/src/seedOperators.ts index af77b307..39bca794 100644 --- a/packages/seeder/src/seedOperators.ts +++ b/packages/seeder/src/seedOperators.ts @@ -1,9 +1,6 @@ +import type prisma from '@prisma/client' +import { type EntityMetadata, defaultMetadata } from './utils/metadata' import { getPrismaClient } from './utils/prismaClient' -import { parseAbiItem } from 'viem' -import { isValidMetadataUrl, validateMetadata } from './utils/metadata' -import type { EntityMetadata } from './utils/metadata' -import { getEigenContracts } from './data/address' -import { getViemClient } from './utils/viemClient' import { baseBlock, bulkUpdateDbTransactions, @@ -13,95 +10,125 @@ import { } from './utils/seeder' const blockSyncKey = 'lastSyncedBlock_operators' +const blockSyncKeyLogs = 'lastSyncedBlock_logs_operators' + +interface OperatorEntryRecord { + metadataUrl: string + metadata: EntityMetadata + isMetadataSynced: boolean + createdAtBlock: bigint + updatedAtBlock: bigint + createdAt: Date + updatedAt: Date +} export async function seedOperators(toBlock?: bigint, fromBlock?: bigint) { - console.log('Seeding Operators ...') - - const viemClient = getViemClient() const prismaClient = getPrismaClient() - const operatorList: Map = new Map() + const operatorList: Map = new Map() const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) - const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() - - // Loop through evm logs - await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { - const logs = await viemClient.getLogs({ - address: getEigenContracts().DelegationManager, - event: parseAbiItem( - 'event OperatorMetadataURIUpdated(address indexed operator, string metadataURI)' - ), - fromBlock, - toBlock - }) - - for (const l in logs) { - const log = logs[l] - - const operatorAddress = String(log.args.operator).toLowerCase() - - try { - if (log.args.metadataURI && isValidMetadataUrl(log.args.metadataURI)) { - const response = await fetch(log.args.metadataURI) - const data = await response.text() - const metadata = validateMetadata(data) - - if (metadata) { - operatorList.set(operatorAddress, metadata) - } else { - throw new Error('Missing operator metadata') + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log( + `[In Sync] [Data] Operator MetadataURI from: ${firstBlock} to: ${lastBlock}` + ) + return + } + + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + const logs = + await prismaClient.eventLogs_OperatorMetadataURIUpdated.findMany({ + where: { + blockNumber: { + gt: fromBlock, + lte: toBlock + } } + }) + + for (const l in logs) { + const log = logs[l] + + const operatorAddress = String(log.operator).toLowerCase() + const existingRecord = operatorList.get(operatorAddress) + + const blockNumber = BigInt(log.blockNumber) + const timestamp = log.blockTime + + if (existingRecord) { + // Operator has been registered before in this fetch + operatorList.set(operatorAddress, { + metadataUrl: log.metadataURI, + metadata: defaultMetadata, + isMetadataSynced: false, + createdAtBlock: existingRecord.createdAtBlock, + updatedAtBlock: blockNumber, + createdAt: existingRecord.createdAt, + updatedAt: timestamp + }) } else { - throw new Error('Invalid operator metadata uri') + // Operator being registered for the first time in this fetch + operatorList.set(operatorAddress, { + metadataUrl: log.metadataURI, + metadata: defaultMetadata, + isMetadataSynced: false, + createdAtBlock: blockNumber, // Will be omitted in upsert if operator exists in db + updatedAtBlock: blockNumber, + createdAt: timestamp, + updatedAt: timestamp + }) } - } catch (error) { - operatorList.set(operatorAddress, { - name: '', - description: '', - discord: '', - logo: '', - telegram: '', - website: '', - x: '' - }) } - } - - console.log( - `Operators registered between blocks ${fromBlock} ${toBlock}: ${logs.length}` - ) - }) + }, + 10_000n + ) // Prepare db transaction object // biome-ignore lint/suspicious/noExplicitAny: const dbTransactions: any[] = [] if (firstBlock === baseBlock) { + dbTransactions.push(prismaClient.avsOperator.deleteMany()) dbTransactions.push(prismaClient.operator.deleteMany()) - const newOperator: { - address: string - metadataName: string - metadataDescription: string - metadataDiscord?: string | null - metadataLogo?: string | null - metadataTelegram?: string | null - metadataWebsite?: string | null - metadataX?: string | null - }[] = [] - - for (const [address, metadata] of operatorList) { + const newOperator: prisma.Operator[] = [] + + for (const [ + address, + { + metadataUrl, + metadata, + isMetadataSynced, + createdAtBlock, + updatedAtBlock, + createdAt, + updatedAt + } + ] of operatorList) { newOperator.push({ address, + metadataUrl, metadataName: metadata.name, metadataDescription: metadata.description, metadataLogo: metadata.logo, metadataDiscord: metadata.discord, metadataTelegram: metadata.telegram, metadataWebsite: metadata.website, - metadataX: metadata.x + metadataX: metadata.x, + isMetadataSynced, + createdAtBlock, + updatedAtBlock, + createdAt, + updatedAt }) } @@ -112,38 +139,60 @@ export async function seedOperators(toBlock?: bigint, fromBlock?: bigint) { }) ) } else { - for (const [address, metadata] of operatorList) { + for (const [ + address, + { + metadataUrl, + metadata, + isMetadataSynced, + createdAtBlock, + updatedAtBlock, + createdAt, + updatedAt + } + ] of operatorList) { dbTransactions.push( prismaClient.operator.upsert({ where: { address }, update: { + metadataUrl, metadataName: metadata.name, metadataDescription: metadata.description, metadataLogo: metadata.logo, metadataDiscord: metadata.discord, metadataTelegram: metadata.telegram, metadataWebsite: metadata.website, - metadataX: metadata.x + metadataX: metadata.x, + isMetadataSynced, + updatedAtBlock, + updatedAt }, create: { address, + metadataUrl, metadataName: metadata.name, metadataDescription: metadata.description, metadataLogo: metadata.logo, metadataDiscord: metadata.discord, metadataTelegram: metadata.telegram, metadataWebsite: metadata.website, - metadataX: metadata.x + metadataX: metadata.x, + isMetadataSynced, + createdAtBlock, + updatedAtBlock, + createdAt, + updatedAt } }) ) } } - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] Operator MetadataURI from: ${firstBlock} to: ${lastBlock} size: ${operatorList.size}` + ) - // Storing last sycned block + // Storing last synced block await saveLastSyncBlock(blockSyncKey, lastBlock) - - console.log('Seeded operators:', operatorList.size) } diff --git a/packages/seeder/src/seedPods.ts b/packages/seeder/src/seedPods.ts index 82d2c93f..ad1c1b85 100644 --- a/packages/seeder/src/seedPods.ts +++ b/packages/seeder/src/seedPods.ts @@ -1,6 +1,4 @@ -import { parseAbiItem } from 'viem' -import { getEigenContracts } from './data/address' -import { getViemClient } from './utils/viemClient' +import type prisma from '@prisma/client' import { getPrismaClient } from './utils/prismaClient' import { baseBlock, @@ -11,6 +9,7 @@ import { } from './utils/seeder' const blockSyncKey = 'lastSyncedBlock_pods' +const blockSyncKeyLogs = 'lastSyncedBlock_logs_pods' /** * @@ -18,45 +17,57 @@ const blockSyncKey = 'lastSyncedBlock_pods' * @param toBlock */ export async function seedPods(toBlock?: bigint, fromBlock?: bigint) { - console.log('Seeding Pods ...') - - const viemClient = getViemClient() const prismaClient = getPrismaClient() - const podList: { address: string; owner: string; blockNumber: bigint }[] = [] + const podList: prisma.Pod[] = [] const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) - const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) - // Loop through evm logs - await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { - const logs = await viemClient.getLogs({ - address: getEigenContracts().EigenPodManager, - event: parseAbiItem( - 'event PodDeployed(address indexed eigenPod, address indexed podOwner)' - ), - fromBlock, - toBlock - }) + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log(`[In Sync] [Data] Pods from: ${firstBlock} to: ${lastBlock}`) + return + } + + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + const logs = await prismaClient.eventLogs_PodDeployed.findMany({ + where: { + blockNumber: { + gt: fromBlock, + lte: toBlock + } + } + }) - for (const l in logs) { - const log = logs[l] + for (const l in logs) { + const log = logs[l] - const podAddress = String(log.args.eigenPod).toLowerCase() - const podOwner = String(log.args.podOwner).toLowerCase() + const podAddress = String(log.eigenPod).toLowerCase() + const podOwner = String(log.podOwner).toLowerCase() - podList.push({ - address: podAddress, - owner: podOwner, - blockNumber: log.blockNumber - }) - } + const blockNumber = BigInt(log.blockNumber) + const timestamp = log.blockTime - console.log( - `Pods deployed between blocks ${fromBlock} ${toBlock}: ${logs.length}` - ) - }) + podList.push({ + address: podAddress, + owner: podOwner, + blockNumber, + createdAtBlock: blockNumber, + updatedAtBlock: blockNumber, + createdAt: timestamp, + updatedAt: timestamp + }) + } + }, + 10_000n + ) // Prepare db transaction object // biome-ignore lint/suspicious/noExplicitAny: @@ -79,22 +90,29 @@ export async function seedPods(toBlock?: bigint, fromBlock?: bigint) { where: { address: pod.address }, update: { owner: pod.owner, - blockNumber: pod.blockNumber + blockNumber: pod.blockNumber, + updatedAtBlock: pod.updatedAtBlock, + updatedAt: pod.updatedAt }, create: { address: pod.address, owner: pod.owner, - blockNumber: pod.blockNumber + blockNumber: pod.blockNumber, + createdAtBlock: pod.createdAtBlock, + createdAt: pod.createdAt, + updatedAtBlock: pod.updatedAtBlock, + updatedAt: pod.updatedAt } }) ) }) } - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] Pods from: ${firstBlock} to: ${lastBlock} size: ${podList.length}` + ) // Storing last sycned block await saveLastSyncBlock(blockSyncKey, lastBlock) - - console.log('Seeded Pods:', podList.length) } diff --git a/packages/seeder/src/seedStakers.ts b/packages/seeder/src/seedStakers.ts index be6966e2..11e33cb2 100644 --- a/packages/seeder/src/seedStakers.ts +++ b/packages/seeder/src/seedStakers.ts @@ -1,146 +1,211 @@ -import { parseAbiItem } from 'viem' -import { getEigenContracts } from './data/address' -import { getViemClient } from './utils/viemClient' +import type prisma from '@prisma/client' import { getPrismaClient } from './utils/prismaClient' import { + type IMap, baseBlock, bulkUpdateDbTransactions, fetchLastSyncBlock, - IMap, loopThroughBlocks, saveLastSyncBlock } from './utils/seeder' const blockSyncKey = 'lastSyncedBlock_stakers' +const blockSyncKeyLogs = 'lastSyncedBlock_logs_stakers' -export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { - console.log('Seeding stakers ...') +interface StakerEntryRecord { + operatorAddress: string | null + shares: { shares: bigint; strategyAddress: string }[] + createdAtBlock: bigint + updatedAtBlock: bigint + createdAt: Date + updatedAt: Date +} - const viemClient = getViemClient() +export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { const prismaClient = getPrismaClient() - const stakers: IMap< - string, - { - operatorAddress: string | null - shares: { shares: bigint; strategyAddress: string }[] - } - > = new Map() + const stakers: IMap = new Map() const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) - const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log(`[In Sync] [Data] Stakers from: ${firstBlock} to: ${lastBlock}`) + return + } if (firstBlock === baseBlock) { await prismaClient.stakerStrategyShares.deleteMany() } - // Loop through evm logs - await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { - // Fetch logs - const logs = await viemClient.getLogs({ - address: getEigenContracts().DelegationManager, - events: [ - parseAbiItem( - 'event StakerDelegated(address indexed staker, address indexed operator)' - ), - parseAbiItem( - 'event StakerUndelegated(address indexed staker, address indexed operator)' - ), - parseAbiItem( - 'event OperatorSharesIncreased(address indexed operator, address staker, address strategy, uint256 shares)' - ), - parseAbiItem( - 'event OperatorSharesDecreased(address indexed operator, address staker, address strategy, uint256 shares)' - ) - ], - fromBlock, - toBlock - }) - - // Stakers list - const stakerAddresses = logs.map((l) => String(l.args.staker).toLowerCase()) - const stakerInit = await prismaClient.staker.findMany({ - where: { address: { in: stakerAddresses } }, - include: { - shares: true - } - }) + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + // biome-ignore lint/suspicious/noExplicitAny: + let allLogs: any[] = [] + + await prismaClient.eventLogs_StakerDelegated + .findMany({ where: { blockNumber: { gt: fromBlock, lte: toBlock } } }) + .then((logs) => { + allLogs = [ + ...allLogs, + ...logs.map((log) => ({ ...log, eventName: 'StakerDelegated' })) + ] + }) - for (const l in logs) { - const log = logs[l] + await prismaClient.eventLogs_StakerUndelegated + .findMany({ where: { blockNumber: { gt: fromBlock, lte: toBlock } } }) + .then((logs) => { + allLogs = [ + ...allLogs, + ...logs.map((log) => ({ ...log, eventName: 'StakerUndelegated' })) + ] + }) - const operatorAddress = String(log.args.operator).toLowerCase() - const stakerAddress = String(log.args.staker).toLowerCase() + await prismaClient.eventLogs_OperatorSharesIncreased + .findMany({ where: { blockNumber: { gt: fromBlock, lte: toBlock } } }) + .then((logs) => { + allLogs = [ + ...allLogs, + ...logs.map((log) => ({ + ...log, + eventName: 'OperatorSharesIncreased' + })) + ] + }) - // Load existing staker shares data - if (!stakers.has(stakerAddress)) { - const foundStakerInit = stakerInit.find( - (s) => s.address.toLowerCase() === stakerAddress.toLowerCase() - ) - if (foundStakerInit) { - stakers.set(stakerAddress, { - operatorAddress: foundStakerInit.operatorAddress, - shares: foundStakerInit.shares.map((s) => ({ - ...s, - shares: BigInt(s.shares) + await prismaClient.eventLogs_OperatorSharesDecreased + .findMany({ where: { blockNumber: { gt: fromBlock, lte: toBlock } } }) + .then((logs) => { + allLogs = [ + ...allLogs, + ...logs.map((log) => ({ + ...log, + eventName: 'OperatorSharesDecreased' })) - }) - } else { - stakers.set(stakerAddress, { - operatorAddress: null, - shares: [] - }) + ] + }) + + // Sort all logs by their block number and log index + allLogs = allLogs.sort((a, b) => { + if (a.blockNumber === b.blockNumber) { + return a.transactionIndex - b.transactionIndex } - } - if (log.eventName === 'StakerDelegated') { - stakers.get(stakerAddress).operatorAddress = operatorAddress - } else if (log.eventName === 'StakerUndelegated') { - stakers.get(stakerAddress).operatorAddress = null - } else if ( - log.eventName === 'OperatorSharesIncreased' || - log.eventName === 'OperatorSharesDecreased' - ) { - const strategyAddress = String(log.args.strategy).toLowerCase() - const shares = log.args.shares - if (!shares) continue - - let foundSharesIndex = stakers - .get(stakerAddress) - .shares.findIndex( - (ss) => - ss.strategyAddress.toLowerCase() === strategyAddress.toLowerCase() + return Number(a.blockNumber - b.blockNumber) + }) + + // Stakers list + const stakerAddresses = allLogs.map((l) => String(l.staker).toLowerCase()) + const stakerInit = + firstBlock !== baseBlock + ? await prismaClient.staker.findMany({ + where: { address: { in: stakerAddresses } }, + include: { + shares: true + } + }) + : [] + + for (const l in allLogs) { + const log = allLogs[l] + + const operatorAddress = String(log.operator).toLowerCase() + const stakerAddress = String(log.staker).toLowerCase() + + const blockNumber = BigInt(log.blockNumber) + const timestamp = log.blockTime + + // Load existing staker shares data + if (!stakers.has(stakerAddress)) { + const foundStakerInit = stakerInit.find( + (s) => s.address.toLowerCase() === stakerAddress.toLowerCase() ) + if (foundStakerInit) { + // Address not in this set of logs but in db + stakers.set(stakerAddress, { + operatorAddress: foundStakerInit.operatorAddress, + shares: foundStakerInit.shares.map((s) => ({ + ...s, + shares: BigInt(s.shares) + })), + createdAtBlock: foundStakerInit.createdAtBlock, + updatedAtBlock: blockNumber, + createdAt: foundStakerInit.createdAt, + updatedAt: timestamp + }) + } else { + // Address neither in this set of logs nor in db + stakers.set(stakerAddress, { + operatorAddress: null, + shares: [], + createdAtBlock: blockNumber, + updatedAtBlock: blockNumber, + createdAt: timestamp, + updatedAt: timestamp + }) + } + } else { + // Address previously found in this set of logs + stakers.get(stakerAddress).updatedAtBlock = blockNumber + stakers.get(stakerAddress).updatedAt = timestamp + } - if (foundSharesIndex !== undefined && foundSharesIndex === -1) { - stakers - .get(stakerAddress) - .shares.push({ shares: 0n, strategyAddress }) + if (log.eventName === 'StakerDelegated') { + stakers.get(stakerAddress).operatorAddress = operatorAddress + } else if (log.eventName === 'StakerUndelegated') { + stakers.get(stakerAddress).operatorAddress = null + } else if ( + log.eventName === 'OperatorSharesIncreased' || + log.eventName === 'OperatorSharesDecreased' + ) { + const strategyAddress = String(log.strategy).toLowerCase() + const shares = BigInt(log.shares) + if (!shares) continue - foundSharesIndex = stakers + let foundSharesIndex = stakers .get(stakerAddress) .shares.findIndex( - (os) => - os.strategyAddress.toLowerCase() === + (ss) => + ss.strategyAddress.toLowerCase() === strategyAddress.toLowerCase() ) - } - if (log.eventName === 'OperatorSharesIncreased') { - stakers.get(stakerAddress).shares[foundSharesIndex].shares = - stakers.get(stakerAddress).shares[foundSharesIndex].shares + shares - } else if (log.eventName === 'OperatorSharesDecreased') { - stakers.get(stakerAddress).shares[foundSharesIndex].shares = - stakers.get(stakerAddress).shares[foundSharesIndex].shares - shares + if (foundSharesIndex !== undefined && foundSharesIndex === -1) { + stakers + .get(stakerAddress) + .shares.push({ shares: 0n, strategyAddress }) + + foundSharesIndex = stakers + .get(stakerAddress) + .shares.findIndex( + (os) => + os.strategyAddress.toLowerCase() === + strategyAddress.toLowerCase() + ) + } + + if (log.eventName === 'OperatorSharesIncreased') { + stakers.get(stakerAddress).shares[foundSharesIndex].shares = + stakers.get(stakerAddress).shares[foundSharesIndex].shares + + shares + } else if (log.eventName === 'OperatorSharesDecreased') { + stakers.get(stakerAddress).shares[foundSharesIndex].shares = + stakers.get(stakerAddress).shares[foundSharesIndex].shares - + shares + } } } - } - console.log( - `Stakers deployed between blocks ${fromBlock} ${toBlock}: ${logs.length}` - ) - }) + console.log(`[Batch] Stakers from: ${fromBlock} to: ${toBlock}`) + }, + 10_000n + ) // biome-ignore lint/suspicious/noExplicitAny: const dbTransactions: any[] = [] @@ -150,17 +215,17 @@ export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { dbTransactions.push(prismaClient.stakerStrategyShares.deleteMany()) dbTransactions.push(prismaClient.staker.deleteMany()) - const newStakers: { address: string; operatorAddress: string | null }[] = [] - const newStakerShares: { - stakerAddress: string - strategyAddress: string - shares: string - }[] = [] + const newStakers: prisma.Staker[] = [] + const newStakerShares: prisma.StakerStrategyShares[] = [] for (const [stakerAddress, stakerDetails] of stakers) { newStakers.push({ address: stakerAddress, - operatorAddress: stakerDetails.operatorAddress + operatorAddress: stakerDetails.operatorAddress, + createdAtBlock: stakerDetails.createdAtBlock, + updatedAtBlock: stakerDetails.updatedAtBlock, + createdAt: stakerDetails.createdAt, + updatedAt: stakerDetails.updatedAt }) stakerDetails.shares.map((share) => { @@ -192,10 +257,16 @@ export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { where: { address: stakerAddress }, create: { address: stakerAddress, - operatorAddress: stakerDetails.operatorAddress + operatorAddress: stakerDetails.operatorAddress, + createdAtBlock: stakerDetails.createdAtBlock, + updatedAtBlock: stakerDetails.updatedAtBlock, + createdAt: stakerDetails.createdAt, + updatedAt: stakerDetails.updatedAt }, update: { - operatorAddress: stakerDetails.operatorAddress + operatorAddress: stakerDetails.operatorAddress, + updatedAtBlock: stakerDetails.updatedAtBlock, + updatedAt: stakerDetails.updatedAt } }) ) @@ -223,10 +294,11 @@ export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { } } - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] Stakers from: ${firstBlock} to: ${lastBlock} size: ${stakers.size}` + ) // Storing last sycned block await saveLastSyncBlock(blockSyncKey, lastBlock) - - console.log('Seeded stakers:', stakers.size) } diff --git a/packages/seeder/src/seedValidators.ts b/packages/seeder/src/seedValidators.ts index 1f4863fb..3d55972c 100644 --- a/packages/seeder/src/seedValidators.ts +++ b/packages/seeder/src/seedValidators.ts @@ -16,7 +16,10 @@ export async function seedValidators(shouldClearPrev?: boolean) { }) const podAddressList = podAddresses.map((p) => p.address.toLowerCase()) - const lastValidatorIndex = await prismaClient.validator.findFirst({ select: { validatorIndex: true }, orderBy: { validatorIndex: 'desc' }}) + const lastValidatorIndex = await prismaClient.validator.findFirst({ + select: { validatorIndex: true }, + orderBy: { validatorIndex: 'desc' } + }) const rpcUrl = process.env.NETWORK_CHAIN_RPC_URL const status = 'finalized' @@ -26,10 +29,17 @@ export async function seedValidators(shouldClearPrev?: boolean) { let isAtEnd = false let batchIndex = 0 - let currentIndex = (!shouldClearPrev && lastValidatorIndex) ? Number(lastValidatorIndex.validatorIndex) + 1 : 0 + let currentIndex = + !shouldClearPrev && lastValidatorIndex + ? Number(lastValidatorIndex.validatorIndex) + 1 + : 0 const chunkSize = 8000 const batchSize = 120_000 - const clearPerv = shouldClearPrev ? shouldClearPrev : lastValidatorIndex?.validatorIndex ? false: true + const clearPerv = shouldClearPrev + ? shouldClearPrev + : lastValidatorIndex?.validatorIndex + ? false + : true while (!isAtEnd) { const validatorRestakeIds = Array.from( @@ -39,7 +49,9 @@ export async function seedValidators(shouldClearPrev?: boolean) { const chunks = chunkArray(validatorRestakeIds, chunkSize) console.log( - `Batch ${batchIndex} from ${currentIndex} - ${currentIndex + batchSize}` + `[Batch] Validator chunk ${batchIndex} from ${currentIndex} - ${ + currentIndex + batchSize + }` ) await Promise.allSettled( @@ -73,12 +85,6 @@ export async function seedValidators(shouldClearPrev?: boolean) { } }) - console.log( - `Batch ${batchIndex} chunk received ${i}`, - validatorsData.data.length, - validators.length - ) - if (validatorsData.data.length < chunkSize) { isAtEnd = true } @@ -103,7 +109,10 @@ export async function seedValidators(shouldClearPrev?: boolean) { }) ) - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] Validator updated size: ${podValidators.length}` + ) console.log('Seeded Validators', podValidators.length) console.timeEnd('Done in') diff --git a/packages/seeder/src/seedWithdrawalsCompleted.ts b/packages/seeder/src/seedWithdrawalsCompleted.ts index 21a2fb2d..d3b82671 100644 --- a/packages/seeder/src/seedWithdrawalsCompleted.ts +++ b/packages/seeder/src/seedWithdrawalsCompleted.ts @@ -1,6 +1,3 @@ -import { parseAbiItem } from 'viem' -import { getEigenContracts } from './data/address' -import { getViemClient } from './utils/viemClient' import { getPrismaClient } from './utils/prismaClient' import { bulkUpdateDbTransactions, @@ -10,6 +7,7 @@ import { } from './utils/seeder' const blockSyncKey = 'lastSyncedBlock_completedWithdrawals' +const blockSyncKeyLogs = 'lastSyncedBlock_logs_completedWithdrawals' /** * @@ -20,40 +18,49 @@ export async function seedCompletedWithdrawals( toBlock?: bigint, fromBlock?: bigint ) { - console.log('Seeding Completed Withdrawals ...') - - const viemClient = getViemClient() const prismaClient = getPrismaClient() const completedWithdrawalList: string[] = [] const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) - const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) + + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log( + `[In Sync] [Data] Completed Withdrawal from: ${firstBlock} to: ${lastBlock}` + ) + return + } - // Loop through evm logs - await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { - const logs = await viemClient.getLogs({ - address: getEigenContracts().DelegationManager, - event: parseAbiItem('event WithdrawalCompleted(bytes32 withdrawalRoot)'), - fromBlock, - toBlock - }) + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + const logs = await prismaClient.eventLogs_WithdrawalCompleted.findMany({ + where: { + blockNumber: { + gt: fromBlock, + lte: toBlock + } + } + }) - for (const l in logs) { - const log = logs[l] + for (const l in logs) { + const log = logs[l] - const withdrawalRoot = log.args.withdrawalRoot + const withdrawalRoot = log.withdrawalRoot - if (withdrawalRoot) { - completedWithdrawalList.push(withdrawalRoot) + if (withdrawalRoot) { + completedWithdrawalList.push(withdrawalRoot) + } } - } - - console.log( - `Withdrawals completed between blocks ${fromBlock} ${toBlock}: ${logs.length}` - ) - }) + }, + 10_000n + ) // Prepare db transaction object // biome-ignore lint/suspicious/noExplicitAny: @@ -70,10 +77,11 @@ export async function seedCompletedWithdrawals( ) } - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] Completed Withdrawal from: ${firstBlock} to: ${lastBlock} size: ${completedWithdrawalList.length}` + ) // // Storing last sycned block await saveLastSyncBlock(blockSyncKey, lastBlock) - - console.log('Seeded Completed Withdrawals:', completedWithdrawalList.length) } diff --git a/packages/seeder/src/seedWithdrawalsQueued.ts b/packages/seeder/src/seedWithdrawalsQueued.ts index 718dd417..5e7c026b 100644 --- a/packages/seeder/src/seedWithdrawalsQueued.ts +++ b/packages/seeder/src/seedWithdrawalsQueued.ts @@ -1,6 +1,4 @@ -import { parseAbiItem } from 'viem' -import { getEigenContracts } from './data/address' -import { getViemClient } from './utils/viemClient' +import prisma from '@prisma/client' import { getPrismaClient } from './utils/prismaClient' import { bulkUpdateDbTransactions, @@ -10,22 +8,7 @@ import { } from './utils/seeder' const blockSyncKey = 'lastSyncedBlock_queuedWithdrawals' - -interface Withdrawal { - withdrawalRoot: string - nonce: number - isCompleted: boolean - - stakerAddress: string - delegatedTo: string - withdrawerAddress: string - strategies: string[] - shares: string[] - startBlock: number - - createdAtBlock: number - updatedAtBlock: number -} +const blockSyncKeyLogs = 'lastSyncedBlock_logs_queuedWithdrawals' /** * @@ -36,63 +19,71 @@ export async function seedQueuedWithdrawals( toBlock?: bigint, fromBlock?: bigint ) { - console.log('Seeding Queued Withdrawals ...') - - const viemClient = getViemClient() const prismaClient = getPrismaClient() - const queuedWithdrawalList: Withdrawal[] = [] + const queuedWithdrawalList: prisma.Withdrawal[] = [] const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) - const lastBlock = toBlock ? toBlock : await viemClient.getBlockNumber() - - // Loop through evm logs - await loopThroughBlocks(firstBlock, lastBlock, async (fromBlock, toBlock) => { - const logs = await viemClient.getLogs({ - address: getEigenContracts().DelegationManager, - event: parseAbiItem([ - 'event WithdrawalQueued(bytes32 withdrawalRoot, Withdrawal withdrawal)', - 'struct Withdrawal { address staker; address delegatedTo; address withdrawer; uint256 nonce; uint32 startBlock; address[] strategies; uint256[] shares; }' - ]), - fromBlock, - toBlock - }) - - for (const l in logs) { - const log = logs[l] - - const withdrawalRoot = log.args.withdrawalRoot - const withdrawal = log.args.withdrawal + const lastBlock = toBlock + ? toBlock + : await fetchLastSyncBlock(blockSyncKeyLogs) - if (withdrawalRoot && withdrawal) { - const stakerAddress = withdrawal.staker.toLowerCase() - const delegatedTo = withdrawal.delegatedTo.toLowerCase() - const withdrawerAddress = withdrawal.withdrawer.toLowerCase() + // Bail early if there is no block diff to sync + if (lastBlock - firstBlock <= 0) { + console.log( + `[In Sync] [Data] Queued Withdrawal from: ${firstBlock} to: ${lastBlock}` + ) + return + } - queuedWithdrawalList.push({ - withdrawalRoot, - nonce: Number(withdrawal.nonce), - isCompleted: false, - stakerAddress, - delegatedTo, - withdrawerAddress, - strategies: withdrawal.strategies.map((s) => - s.toLowerCase() - ) as string[], - shares: withdrawal.shares.map((s) => BigInt(s).toString()), - startBlock: withdrawal.startBlock, + await loopThroughBlocks( + firstBlock, + lastBlock, + async (fromBlock, toBlock) => { + const logs = await prismaClient.eventLogs_WithdrawalQueued.findMany({ + where: { + blockNumber: { + gt: fromBlock, + lte: toBlock + } + } + }) - createdAtBlock: Number(log.blockNumber), - updatedAtBlock: Number(log.blockNumber) - }) + for (const l in logs) { + const log = logs[l] + + const withdrawalRoot = log.withdrawalRoot + + const blockNumber = BigInt(log.blockNumber) + const timestamp = log.blockTime + + if (withdrawalRoot) { + const stakerAddress = log.staker.toLowerCase() + const delegatedTo = log.delegatedTo.toLowerCase() + const withdrawerAddress = log.withdrawer.toLowerCase() + + queuedWithdrawalList.push({ + withdrawalRoot, + nonce: Number(log.nonce), + isCompleted: false, + stakerAddress, + delegatedTo, + withdrawerAddress, + strategies: log.strategies.map((s) => s.toLowerCase()) as string[], + shares: log.shares.map((s) => s.toString()), + + startBlock: log.startBlock, + createdAtBlock: blockNumber, + updatedAtBlock: blockNumber, + createdAt: timestamp, + updatedAt: timestamp + }) + } } - } - - console.log( - `Withdrawals queued between blocks ${fromBlock} ${toBlock}: ${logs.length}` - ) - }) + }, + 10_000n + ) // Prepare db transaction object // biome-ignore lint/suspicious/noExplicitAny: @@ -107,10 +98,11 @@ export async function seedQueuedWithdrawals( ) } - await bulkUpdateDbTransactions(dbTransactions) + await bulkUpdateDbTransactions( + dbTransactions, + `[Data] Queued Withdrawal from: ${firstBlock} to: ${lastBlock} size: ${queuedWithdrawalList.length}` + ) // // Storing last sycned block await saveLastSyncBlock(blockSyncKey, lastBlock) - - console.log('Seeded Queued Withdrawals:', queuedWithdrawalList.length) } diff --git a/packages/seeder/src/utils/metadata.ts b/packages/seeder/src/utils/metadata.ts index 4bbe6159..259405a0 100644 --- a/packages/seeder/src/utils/metadata.ts +++ b/packages/seeder/src/utils/metadata.ts @@ -8,6 +8,16 @@ export interface EntityMetadata { x: string } +export const defaultMetadata: EntityMetadata = { + name: '', + website: '', + description: '', + logo: '', + x: '', + discord: '', + telegram: '' +} + export function isValidMetadataUrl(url: string): boolean { // Define the regular expression pattern for HTTPS URLs const httpsUrlPattern = diff --git a/packages/seeder/src/utils/seeder.ts b/packages/seeder/src/utils/seeder.ts index 67300888..b7fc4545 100644 --- a/packages/seeder/src/utils/seeder.ts +++ b/packages/seeder/src/utils/seeder.ts @@ -15,38 +15,43 @@ export const baseBlock = export async function loopThroughBlocks( firstBlock: bigint, lastBlock: bigint, - cb: (fromBlock: bigint, toBlock: bigint) => Promise + cb: (fromBlock: bigint, toBlock: bigint) => Promise, + defaultBatchSize?: bigint ) { + const batchSize = defaultBatchSize ? defaultBatchSize : 4999n let currentBlock = firstBlock let nextBlock = firstBlock while (nextBlock < lastBlock) { - nextBlock = currentBlock + 4999n + nextBlock = currentBlock + batchSize if (nextBlock >= lastBlock) nextBlock = lastBlock await cb(currentBlock, nextBlock) - currentBlock = nextBlock + 1n + currentBlock = nextBlock } return lastBlock } -// biome-ignore lint/suspicious/noExplicitAny: -export async function bulkUpdateDbTransactions(dbTransactions: any[]) { +export async function bulkUpdateDbTransactions( + // biome-ignore lint/suspicious/noExplicitAny: + dbTransactions: any[], + label?: string +) { const prismaClient = getPrismaClient() const chunkSize = 1000 let i = 0 - console.log('Updating db transactions', dbTransactions.length) + console.time(`[DB Write (${dbTransactions.length})] ${label || ''}`) for (const chunk of chunkArray(dbTransactions, chunkSize)) { - console.time(`Updating db transactions ${i}, size: ${chunk.length}`) await prismaClient.$transaction(chunk) - console.timeEnd(`Updating db transactions ${i}, size: ${chunk.length}`) i++ } + + console.timeEnd(`[DB Write (${dbTransactions.length})] ${label || ''}`) } export async function fetchLastSyncBlock(key: string): Promise { @@ -57,7 +62,7 @@ export async function fetchLastSyncBlock(key: string): Promise { }) return lastSyncedBlockData?.value - ? BigInt(lastSyncedBlockData.value as number) + 1n + ? BigInt(lastSyncedBlockData.value as number) : baseBlock } @@ -70,3 +75,45 @@ export async function saveLastSyncBlock(key: string, blockNumber: bigint) { create: { key: key, value: Number(blockNumber) } }) } + +export async function getBlockDataFromDb(fromBlock: bigint, toBlock: bigint) { + const prismaClient = getPrismaClient() + + const blockData = await prismaClient.evm_BlockData.findMany({ + where: { + number: { + gte: BigInt(fromBlock), + lte: BigInt(toBlock) + } + }, + select: { + number: true, + timestamp: true + }, + orderBy: { + number: 'asc' + } + }) + + return new Map(blockData.map((block) => [block.number, block.timestamp])) +} + +export async function fetchWithTimeout( + url: string, + timeout = 5000 +): Promise { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeout) + + try { + const response = await fetch(url, { signal: controller.signal }) + return response + } catch (error) { + if (error.name === 'AbortError') { + throw new Error('Request timed out') + } + throw error + } finally { + clearTimeout(timeoutId) + } +} \ No newline at end of file