From 06ef8af8666f897d5b293cf20d78e656dc9b2868 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:18:59 -0300 Subject: [PATCH 01/32] build: separate tsconfig for build stage --- tsconfig.base.json | 23 +++++++++++++++++++++++ tsconfig.build.json | 8 ++++++++ tsconfig.json | 22 ++-------------------- 3 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 tsconfig.base.json create mode 100644 tsconfig.build.json diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..14527c8 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,23 @@ +/* Based on total-typescript no-dom app config */ +{ + "compilerOptions": { + /* Base Options: */ + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": false, + /* Strictness */ + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + /* If transpiling with TypeScript: */ + "module": "NodeNext", + "sourceMap": true, + /* If your code doesn't run in the DOM: */ + "lib": ["es2022"] + } +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..13862a6 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,8 @@ +/* Based on total-typescript no-dom app config */ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "incremental": false, + "noEmit": false + } +} diff --git a/tsconfig.json b/tsconfig.json index d077d24..98d1e21 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,5 @@ /* Based on total-typescript no-dom app config */ { - "compilerOptions": { - /* Base Options: */ - "esModuleInterop": true, - "skipLibCheck": true, - "target": "es2022", - "allowJs": true, - "resolveJsonModule": true, - "moduleDetection": "force", - "isolatedModules": true, - "verbatimModuleSyntax": true, - /* Strictness */ - "strict": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - /* If transpiling with TypeScript: */ - "module": "NodeNext", - "sourceMap": true, - /* If your code doesn't run in the DOM: */ - "lib": ["es2022"] - } + "extends": "./tsconfig.base.json", + "include": ["**/*", ".*.js"] } From 9802a896ca500ed5f9cf685765d3b6a8716c6841 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:19:49 -0300 Subject: [PATCH 02/32] feat: set up blocknumber module tests --- package.json | 7 +++++-- packages/.gitkeep | 0 packages/blocknumber/package.json | 17 +++++++++++++++++ packages/blocknumber/src/helloWorld.spec.ts | 7 +++++++ packages/blocknumber/tsconfig.build.json | 8 ++++++++ packages/blocknumber/tsconfig.json | 4 ++++ packages/blocknumber/vitest.config.ts | 21 +++++++++++++++++++++ 7 files changed, 62 insertions(+), 2 deletions(-) delete mode 100644 packages/.gitkeep create mode 100644 packages/blocknumber/package.json create mode 100644 packages/blocknumber/src/helloWorld.spec.ts create mode 100644 packages/blocknumber/tsconfig.build.json create mode 100644 packages/blocknumber/tsconfig.json create mode 100644 packages/blocknumber/vitest.config.ts diff --git a/package.json b/package.json index f2b9e60..66f1145 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,13 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "build": "turbo run build", + "test": "turbo run test", "prepare": "husky", "lint": "turbo run lint", - "format": "turbo run format" + "lint:fix": "turbo run lint:fix", + "format": "turbo run format", + "format:fix": "turbo run format:fix" }, "keywords": [], "author": "", diff --git a/packages/.gitkeep b/packages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json new file mode 100644 index 0000000..49b5377 --- /dev/null +++ b/packages/blocknumber/package.json @@ -0,0 +1,17 @@ +{ + "name": "blocknumber", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "build": "tsc", + "lint": "eslint .", + "lint:fix": "pnpm lint --fix", + "format": "prettier --check .", + "format:fix": "prettier --write .", + "test": "vitest run" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts new file mode 100644 index 0000000..7216041 --- /dev/null +++ b/packages/blocknumber/src/helloWorld.spec.ts @@ -0,0 +1,7 @@ +import { describe, expect, it } from "vitest"; + +describe("test", () => { + it("pass", () => { + expect(1).toBe(1); + }); +}); diff --git a/packages/blocknumber/tsconfig.build.json b/packages/blocknumber/tsconfig.build.json new file mode 100644 index 0000000..7e50390 --- /dev/null +++ b/packages/blocknumber/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["**/*.spec.ts"] +} diff --git a/packages/blocknumber/tsconfig.json b/packages/blocknumber/tsconfig.json new file mode 100644 index 0000000..66bb87a --- /dev/null +++ b/packages/blocknumber/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*"] +} diff --git a/packages/blocknumber/vitest.config.ts b/packages/blocknumber/vitest.config.ts new file mode 100644 index 0000000..84e59e7 --- /dev/null +++ b/packages/blocknumber/vitest.config.ts @@ -0,0 +1,21 @@ +import path from "path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, // Use Vitest's global API without importing it in each file + environment: "node", // Use the Node.js environment + include: ["src/**/*.spec.ts"], // Include test files + exclude: ["node_modules", "dist"], // Exclude certain directories + coverage: { + reporter: ["text", "json", "html"], // Coverage reporters + exclude: ["node_modules", "dist", "src/**/*.d.ts"], // Files to exclude from coverage + }, + }, + resolve: { + alias: { + // Setup path alias based on tsconfig paths + "@": path.resolve(__dirname, "src"), + }, + }, +}); From 546d76ea14735435f69ea23a77084bfff1f6932f Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:24:34 -0300 Subject: [PATCH 03/32] chore: set up blocknumber module linting --- packages/blocknumber/.lintstagedrc.js | 5 +++++ packages/blocknumber/.prettierignore | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 packages/blocknumber/.lintstagedrc.js create mode 100644 packages/blocknumber/.prettierignore diff --git a/packages/blocknumber/.lintstagedrc.js b/packages/blocknumber/.lintstagedrc.js new file mode 100644 index 0000000..85ad482 --- /dev/null +++ b/packages/blocknumber/.lintstagedrc.js @@ -0,0 +1,5 @@ +const baseConfig = require("../../.lintstagedrc.js"); + +module.exports = { + ...baseConfig, +}; diff --git a/packages/blocknumber/.prettierignore b/packages/blocknumber/.prettierignore new file mode 100644 index 0000000..319ebdd --- /dev/null +++ b/packages/blocknumber/.prettierignore @@ -0,0 +1,4 @@ +# Generated files +pnpm-lock.yaml +node_modules +dist \ No newline at end of file From f291b4582cb08c985d843b98238e2d59f3a6746c Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:48:56 -0300 Subject: [PATCH 04/32] style: remove total-typescript on non-base tsconfig --- packages/blocknumber/.lintstagedrc.js | 5 ----- tsconfig.build.json | 1 - tsconfig.json | 1 - 3 files changed, 7 deletions(-) delete mode 100644 packages/blocknumber/.lintstagedrc.js diff --git a/packages/blocknumber/.lintstagedrc.js b/packages/blocknumber/.lintstagedrc.js deleted file mode 100644 index 85ad482..0000000 --- a/packages/blocknumber/.lintstagedrc.js +++ /dev/null @@ -1,5 +0,0 @@ -const baseConfig = require("../../.lintstagedrc.js"); - -module.exports = { - ...baseConfig, -}; diff --git a/tsconfig.build.json b/tsconfig.build.json index 13862a6..45a228e 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,3 @@ -/* Based on total-typescript no-dom app config */ { "extends": "./tsconfig.base.json", "compilerOptions": { diff --git a/tsconfig.json b/tsconfig.json index 98d1e21..d48eef2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,3 @@ -/* Based on total-typescript no-dom app config */ { "extends": "./tsconfig.base.json", "include": ["**/*", ".*.js"] From 0e8fc5ad3e349a8546bdbe8a61b25b4e2cfa2459 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:55:53 -0300 Subject: [PATCH 05/32] build: set up build script --- packages/blocknumber/.gitignore | 1 + packages/blocknumber/package.json | 2 +- packages/blocknumber/src/helloWorld.spec.ts | 6 +++++- packages/blocknumber/src/helloWorld.ts | 3 +++ 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 packages/blocknumber/.gitignore create mode 100644 packages/blocknumber/src/helloWorld.ts diff --git a/packages/blocknumber/.gitignore b/packages/blocknumber/.gitignore new file mode 100644 index 0000000..7773828 --- /dev/null +++ b/packages/blocknumber/.gitignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json index 49b5377..7df4b85 100644 --- a/packages/blocknumber/package.json +++ b/packages/blocknumber/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.ts", "scripts": { - "build": "tsc", + "build": "tsc -p tsconfig.build.json", "lint": "eslint .", "lint:fix": "pnpm lint --fix", "format": "prettier --check .", diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts index 7216041..f2cafa8 100644 --- a/packages/blocknumber/src/helloWorld.spec.ts +++ b/packages/blocknumber/src/helloWorld.spec.ts @@ -1,7 +1,11 @@ import { describe, expect, it } from "vitest"; +import foo from "./helloWorld"; + describe("test", () => { it("pass", () => { - expect(1).toBe(1); + const result = foo(); + + expect(result).toBe("bar"); }); }); diff --git a/packages/blocknumber/src/helloWorld.ts b/packages/blocknumber/src/helloWorld.ts new file mode 100644 index 0000000..0838c22 --- /dev/null +++ b/packages/blocknumber/src/helloWorld.ts @@ -0,0 +1,3 @@ +export default function foo() { + return "bar"; +} From baa144abfccd29b6122d6e448bf9fba5a32302d8 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 09:50:38 -0300 Subject: [PATCH 06/32] fix: blocknumber module package config --- .gitignore | 5 ++++- packages/blocknumber/.gitignore | 1 - packages/blocknumber/.prettierignore | 1 - packages/blocknumber/package.json | 1 + packages/blocknumber/tsconfig.build.json | 3 ++- 5 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 packages/blocknumber/.gitignore diff --git a/.gitignore b/.gitignore index 4b6f676..8366b82 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ node_modules/ # Turbo -.turbo/ \ No newline at end of file +.turbo/ + +# Build +dist/ \ No newline at end of file diff --git a/packages/blocknumber/.gitignore b/packages/blocknumber/.gitignore deleted file mode 100644 index 7773828..0000000 --- a/packages/blocknumber/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist/ \ No newline at end of file diff --git a/packages/blocknumber/.prettierignore b/packages/blocknumber/.prettierignore index 319ebdd..3da65e6 100644 --- a/packages/blocknumber/.prettierignore +++ b/packages/blocknumber/.prettierignore @@ -1,4 +1,3 @@ # Generated files -pnpm-lock.yaml node_modules dist \ No newline at end of file diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json index 7df4b85..9ddd104 100644 --- a/packages/blocknumber/package.json +++ b/packages/blocknumber/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.ts", + "type": "module", "scripts": { "build": "tsc -p tsconfig.build.json", "lint": "eslint .", diff --git a/packages/blocknumber/tsconfig.build.json b/packages/blocknumber/tsconfig.build.json index 7e50390..4b3e8f3 100644 --- a/packages/blocknumber/tsconfig.build.json +++ b/packages/blocknumber/tsconfig.build.json @@ -1,8 +1,9 @@ { "extends": "../../tsconfig.build.json", "compilerOptions": { + "declaration": true, "outDir": "dist" }, "include": ["src/**/*"], - "exclude": ["**/*.spec.ts"] + "exclude": ["node_modules", "build", "tests", "vitest.config.ts"] } From 9965c35b38837e42c4b9c96f95e5833cbcb01f89 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 09:58:07 -0300 Subject: [PATCH 07/32] fix: dummy class import module --- packages/blocknumber/src/helloWorld.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts index f2cafa8..bdcee5e 100644 --- a/packages/blocknumber/src/helloWorld.spec.ts +++ b/packages/blocknumber/src/helloWorld.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import foo from "./helloWorld"; +import foo from "./helloWorld.js"; describe("test", () => { it("pass", () => { From 6215697974ad0d7ac5b6da061e4db76de9b27f9f Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 15:14:36 -0300 Subject: [PATCH 08/32] chore: add coverage script and tasks --- .gitignore | 5 +- package.json | 2 + packages/blocknumber/package.json | 3 +- packages/blocknumber/vitest.config.ts | 1 + pnpm-lock.yaml | 305 ++++++++++++++++++++++++++ turbo.json | 1 + 6 files changed, 315 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8366b82..d9cc783 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ node_modules/ .turbo/ # Build -dist/ \ No newline at end of file +dist/ + +# Coverage +coverage/ \ No newline at end of file diff --git a/package.json b/package.json index 66f1145..abfffdc 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "turbo run build", "test": "turbo run test", + "coverage": "turbo run coverage", "prepare": "husky", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", @@ -21,6 +22,7 @@ "@ianvs/prettier-plugin-sort-imports": "4.3.1", "@typescript-eslint/eslint-plugin": "7.16.1", "@typescript-eslint/parser": "7.16.1", + "@vitest/coverage-v8": "^2.0.3", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.2.1", diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json index 9ddd104..90949f5 100644 --- a/packages/blocknumber/package.json +++ b/packages/blocknumber/package.json @@ -10,7 +10,8 @@ "lint:fix": "pnpm lint --fix", "format": "prettier --check .", "format:fix": "prettier --write .", - "test": "vitest run" + "test": "vitest run", + "coverage": "vitest run --coverage" }, "keywords": [], "author": "", diff --git a/packages/blocknumber/vitest.config.ts b/packages/blocknumber/vitest.config.ts index 84e59e7..fec3430 100644 --- a/packages/blocknumber/vitest.config.ts +++ b/packages/blocknumber/vitest.config.ts @@ -8,6 +8,7 @@ export default defineConfig({ include: ["src/**/*.spec.ts"], // Include test files exclude: ["node_modules", "dist"], // Exclude certain directories coverage: { + provider: "v8", reporter: ["text", "json", "html"], // Coverage reporters exclude: ["node_modules", "dist", "src/**/*.d.ts"], // Files to exclude from coverage }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36a1010..56d01ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ importers: "@typescript-eslint/parser": specifier: 7.16.1 version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + "@vitest/coverage-v8": + specifier: ^2.0.3 + version: 2.0.3(vitest@2.0.3(@types/node@20.14.11)) eslint: specifier: 8.57.0 version: 8.57.0 @@ -50,6 +53,8 @@ importers: specifier: 2.0.3 version: 2.0.3(@types/node@20.14.11) + packages/blocknumber: {} + packages: "@ampproject/remapping@2.3.0": resolution: @@ -208,6 +213,12 @@ packages: } engines: { node: ">=6.9.0" } + "@bcoe/v8-coverage@0.2.3": + resolution: + { + integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, + } + "@commitlint/cli@19.3.0": resolution: { @@ -599,6 +610,20 @@ packages: "@vue/compiler-sfc": optional: true + "@isaacs/cliui@8.0.2": + resolution: + { + integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, + } + engines: { node: ">=12" } + + "@istanbuljs/schema@0.1.3": + resolution: + { + integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, + } + engines: { node: ">=8" } + "@jridgewell/gen-mapping@0.3.5": resolution: { @@ -653,6 +678,13 @@ packages: } engines: { node: ">= 8" } + "@pkgjs/parseargs@0.11.0": + resolution: + { + integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, + } + engines: { node: ">=14" } + "@pkgr/core@0.1.1": resolution: { @@ -894,6 +926,14 @@ packages: integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==, } + "@vitest/coverage-v8@2.0.3": + resolution: + { + integrity: sha512-53d+6jXFdYbasXBmsL6qaGIfcY5eBQq0sP57AjdasOcSiGNj4qxkkpDKIitUNfjxcfAfUfQ8BD0OR2fSey64+g==, + } + peerDependencies: + vitest: 2.0.3 + "@vitest/expect@2.0.3": resolution: { @@ -1303,6 +1343,12 @@ packages: } engines: { node: ">=8" } + eastasianwidth@0.2.0: + resolution: + { + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, + } + electron-to-chromium@1.4.829: resolution: { @@ -1321,6 +1367,12 @@ packages: integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, } + emoji-regex@9.2.2: + resolution: + { + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, + } + env-paths@2.2.1: resolution: { @@ -1549,6 +1601,13 @@ packages: integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==, } + foreground-child@3.2.1: + resolution: + { + integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==, + } + engines: { node: ">=14" } + fs.realpath@1.0.0: resolution: { @@ -1619,6 +1678,13 @@ packages: } engines: { node: ">=10.13.0" } + glob@10.4.5: + resolution: + { + integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==, + } + hasBin: true + glob@7.2.3: resolution: { @@ -1674,6 +1740,12 @@ packages: } engines: { node: ">=8" } + html-escaper@2.0.2: + resolution: + { + integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, + } + human-signals@5.0.0: resolution: { @@ -1818,6 +1890,40 @@ packages: integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, } + istanbul-lib-coverage@3.2.2: + resolution: + { + integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, + } + engines: { node: ">=8" } + + istanbul-lib-report@3.0.1: + resolution: + { + integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, + } + engines: { node: ">=10" } + + istanbul-lib-source-maps@5.0.6: + resolution: + { + integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==, + } + engines: { node: ">=10" } + + istanbul-reports@3.1.7: + resolution: + { + integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==, + } + engines: { node: ">=8" } + + jackspeak@3.4.3: + resolution: + { + integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, + } + jiti@1.21.6: resolution: { @@ -1831,6 +1937,12 @@ packages: integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, } + js-tokens@9.0.0: + resolution: + { + integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==, + } + js-yaml@4.1.0: resolution: { @@ -2013,6 +2125,12 @@ packages: integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==, } + lru-cache@10.4.3: + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, + } + lru-cache@5.1.1: resolution: { @@ -2025,6 +2143,19 @@ packages: integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==, } + magicast@0.3.4: + resolution: + { + integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==, + } + + make-dir@4.0.0: + resolution: + { + integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, + } + engines: { node: ">=10" } + meow@12.1.1: resolution: { @@ -2085,6 +2216,13 @@ packages: integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, } + minipass@7.1.2: + resolution: + { + integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, + } + engines: { node: ">=16 || 14 >=14.17" } + ms@2.1.2: resolution: { @@ -2173,6 +2311,12 @@ packages: } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + package-json-from-dist@1.0.0: + resolution: + { + integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==, + } + parent-module@1.0.1: resolution: { @@ -2222,6 +2366,13 @@ packages: } engines: { node: ">=12" } + path-scurry@1.11.1: + resolution: + { + integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==, + } + engines: { node: ">=16 || 14 >=14.18" } + path-type@4.0.0: resolution: { @@ -2484,6 +2635,13 @@ packages: } engines: { node: ">=8" } + string-width@5.1.2: + resolution: + { + integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, + } + engines: { node: ">=12" } + string-width@7.2.0: resolution: { @@ -2519,6 +2677,12 @@ packages: } engines: { node: ">=8" } + strip-literal@2.1.0: + resolution: + { + integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==, + } + supports-color@5.5.0: resolution: { @@ -2540,6 +2704,13 @@ packages: } engines: { node: ^14.18.0 || >=16.0.0 } + test-exclude@7.0.1: + resolution: + { + integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==, + } + engines: { node: ">=18" } + text-extensions@2.4.0: resolution: { @@ -2817,6 +2988,13 @@ packages: } engines: { node: ">=10" } + wrap-ansi@8.1.0: + resolution: + { + integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, + } + engines: { node: ">=12" } + wrap-ansi@9.0.0: resolution: { @@ -3018,6 +3196,8 @@ snapshots: "@babel/helper-validator-identifier": 7.24.7 to-fast-properties: 2.0.0 + "@bcoe/v8-coverage@0.2.3": {} + "@commitlint/cli@19.3.0(@types/node@20.14.11)(typescript@5.5.3)": dependencies: "@commitlint/format": 19.3.0 @@ -3245,6 +3425,17 @@ snapshots: transitivePeerDependencies: - supports-color + "@isaacs/cliui@8.0.2": + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + "@istanbuljs/schema@0.1.3": {} + "@jridgewell/gen-mapping@0.3.5": dependencies: "@jridgewell/set-array": 1.2.1 @@ -3274,6 +3465,9 @@ snapshots: "@nodelib/fs.scandir": 2.1.5 fastq: 1.17.1 + "@pkgjs/parseargs@0.11.0": + optional: true + "@pkgr/core@0.1.1": {} "@rollup/rollup-android-arm-eabi@4.18.1": @@ -3417,6 +3611,25 @@ snapshots: "@ungap/structured-clone@1.2.0": {} + "@vitest/coverage-v8@2.0.3(vitest@2.0.3(@types/node@20.14.11))": + dependencies: + "@ampproject/remapping": 2.3.0 + "@bcoe/v8-coverage": 0.2.3 + debug: 4.3.5 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.10 + magicast: 0.3.4 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.0.3(@types/node@20.14.11) + transitivePeerDependencies: + - supports-color + "@vitest/expect@2.0.3": dependencies: "@vitest/spy": 2.0.3 @@ -3649,12 +3862,16 @@ snapshots: dependencies: is-obj: 2.0.0 + eastasianwidth@0.2.0: {} + electron-to-chromium@1.4.829: {} emoji-regex@10.3.0: {} emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + env-paths@2.2.1: {} error-ex@1.3.2: @@ -3841,6 +4058,11 @@ snapshots: flatted@3.3.1: {} + foreground-child@3.2.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -3870,6 +4092,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.4.5: + dependencies: + foreground-child: 3.2.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3904,6 +4135,8 @@ snapshots: has-flag@4.0.0: {} + html-escaper@2.0.2: {} + human-signals@5.0.0: {} husky@9.1.0: {} @@ -3958,10 +4191,39 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + "@jridgewell/trace-mapping": 0.3.25 + debug: 4.3.5 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + "@isaacs/cliui": 8.0.2 + optionalDependencies: + "@pkgjs/parseargs": 0.11.0 + jiti@1.21.6: {} js-tokens@4.0.0: {} + js-tokens@9.0.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -4057,6 +4319,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + lru-cache@10.4.3: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4065,6 +4329,16 @@ snapshots: dependencies: "@jridgewell/sourcemap-codec": 1.5.0 + magicast@0.3.4: + dependencies: + "@babel/parser": 7.24.8 + "@babel/types": 7.24.9 + source-map-js: 1.2.0 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + meow@12.1.1: {} merge-stream@2.0.0: {} @@ -4090,6 +4364,8 @@ snapshots: minimist@1.2.8: {} + minipass@7.1.2: {} + ms@2.1.2: {} nanoid@3.3.7: {} @@ -4139,6 +4415,8 @@ snapshots: dependencies: p-limit: 4.0.0 + package-json-from-dist@1.0.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -4160,6 +4438,11 @@ snapshots: path-key@4.0.0: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-type@4.0.0: {} pathe@1.1.2: {} @@ -4281,6 +4564,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + string-width@7.2.0: dependencies: emoji-regex: 10.3.0 @@ -4299,6 +4588,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@2.1.0: + dependencies: + js-tokens: 9.0.0 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -4312,6 +4605,12 @@ snapshots: "@pkgr/core": 0.1.1 tslib: 2.6.3 + test-exclude@7.0.1: + dependencies: + "@istanbuljs/schema": 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-extensions@2.4.0: {} text-table@0.2.0: {} @@ -4462,6 +4761,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 diff --git a/turbo.json b/turbo.json index 692f067..541985e 100644 --- a/turbo.json +++ b/turbo.json @@ -6,6 +6,7 @@ "format": {}, "format:fix": {}, "test": {}, + "coverage": {}, "build": {} }, "globalDependencies": [".eslintrc", ".prettierrc"] From 2748ca2c37e4881bb0e7276778661a71940bff3e Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 12:33:46 -0300 Subject: [PATCH 09/32] chore: remove dummy classes --- packages/blocknumber/src/helloWorld.spec.ts | 11 ----------- packages/blocknumber/src/helloWorld.ts | 3 --- 2 files changed, 14 deletions(-) delete mode 100644 packages/blocknumber/src/helloWorld.spec.ts delete mode 100644 packages/blocknumber/src/helloWorld.ts diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts deleted file mode 100644 index bdcee5e..0000000 --- a/packages/blocknumber/src/helloWorld.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import foo from "./helloWorld.js"; - -describe("test", () => { - it("pass", () => { - const result = foo(); - - expect(result).toBe("bar"); - }); -}); diff --git a/packages/blocknumber/src/helloWorld.ts b/packages/blocknumber/src/helloWorld.ts deleted file mode 100644 index 0838c22..0000000 --- a/packages/blocknumber/src/helloWorld.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function foo() { - return "bar"; -} From ab869a4011e61e11ac20e001d3dc86716a46ade3 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 12:34:29 -0300 Subject: [PATCH 10/32] feat: implement caip-2 compliant chain ids --- packages/blocknumber/src/index.ts | 0 packages/blocknumber/src/utils/chainId.ts | 72 +++++++++++++++++++ packages/blocknumber/src/utils/index.ts | 1 + .../blocknumber/test/utils/chainId.spec.ts | 40 +++++++++++ 4 files changed, 113 insertions(+) create mode 100644 packages/blocknumber/src/index.ts create mode 100644 packages/blocknumber/src/utils/chainId.ts create mode 100644 packages/blocknumber/src/utils/index.ts create mode 100644 packages/blocknumber/test/utils/chainId.spec.ts diff --git a/packages/blocknumber/src/index.ts b/packages/blocknumber/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/blocknumber/src/utils/chainId.ts b/packages/blocknumber/src/utils/chainId.ts new file mode 100644 index 0000000..ba6394d --- /dev/null +++ b/packages/blocknumber/src/utils/chainId.ts @@ -0,0 +1,72 @@ +// Based on https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md + +type ChainNamespace = string; +type ChainReference = string; + +interface ChainIdParams { + namespace: ChainNamespace; + reference: ChainReference; +} + +const NAMESPACE_FORMAT = /^[-a-z0-9]{3,8}$/; +const REFERENCE_FORMAT = /^[-_a-zA-Z0-9]{1,32}$/; + +export class InvalidChainId extends Error { + constructor(message: string) { + super(message); + + this.name = "InvalidChainId"; + } +} + +export class ChainId { + private namespace: string; + private reference: string; + + /** + * Creates a validated CAIP-2 compliant chain ID. + * + * @param chainId a CAIP-2 compliant string. + */ + constructor(chainId: string) { + const params = ChainId.parse(chainId); + + this.namespace = params.namespace; + this.reference = params.reference; + } + + /** + * Parses a CAIP-2 compliant string. + * + * @param chainId {string} a CAIP-2 compliant string + * @returns an object containing the namespace and the reference of the chain id + */ + public static parse(chainId: string): ChainIdParams { + const elements = chainId.split(":"); + + if (elements.length !== 2) { + throw new InvalidChainId("A CAIP-2 chain id should have exactly one colon."); + } + + const [namespace, reference] = elements; + + if (namespace === undefined || reference === undefined) { + throw new InvalidChainId("Both elements should be defined."); + } + + const isValidNamespace = NAMESPACE_FORMAT.test(namespace); + if (!isValidNamespace) throw new InvalidChainId("Chain ID namespace is not valid."); + + const isValidReference = REFERENCE_FORMAT.test(reference); + if (!isValidReference) throw new InvalidChainId("Chain ID reference is not valid."); + + return { + namespace, + reference, + }; + } + + public toString() { + return `${this.namespace}:${this.reference}`; + } +} diff --git a/packages/blocknumber/src/utils/index.ts b/packages/blocknumber/src/utils/index.ts new file mode 100644 index 0000000..8e507d6 --- /dev/null +++ b/packages/blocknumber/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./chainId.js"; diff --git a/packages/blocknumber/test/utils/chainId.spec.ts b/packages/blocknumber/test/utils/chainId.spec.ts new file mode 100644 index 0000000..4e48c92 --- /dev/null +++ b/packages/blocknumber/test/utils/chainId.spec.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; + +import { ChainId, InvalidChainId } from "../../src/utils/chainId.js"; + +describe("ChainId", () => { + describe("constructor", () => { + it("creates a valid chain id instance", () => { + const chainId = new ChainId("eip155:1"); + + expect(chainId).toBeInstanceOf(ChainId); + }); + + it("fails when input chain id is not caip-2 compliant", () => { + const chainId = "foobar"; + + expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); + }); + + it("fails when input namespace is not caip-2 compliant", () => { + const chainId = "f:1"; + + expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); + }); + + it("fails when input reference is not caip-2 compliant", () => { + const chainId = "foo:!nval!d"; + + expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); + }); + }); + + describe("toString", () => { + it("returns a CAIP-2 compliant string", () => { + const ethChainId = "eip155:1"; + const chainId = new ChainId(ethChainId); + + expect(chainId.toString()).toEqual(ethChainId); + }); + }); +}); From 212960ac35ec2e542a08fcacc709aeee765982ed Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 24 Jul 2024 11:55:51 -0300 Subject: [PATCH 11/32] refactor: create exceptions/ folder --- packages/blocknumber/src/exceptions/index.ts | 1 + packages/blocknumber/src/exceptions/invalidChain.ts | 7 +++++++ packages/blocknumber/src/utils/chainId.ts | 10 ++-------- packages/blocknumber/test/utils/chainId.spec.ts | 3 ++- 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/index.ts create mode 100644 packages/blocknumber/src/exceptions/invalidChain.ts diff --git a/packages/blocknumber/src/exceptions/index.ts b/packages/blocknumber/src/exceptions/index.ts new file mode 100644 index 0000000..0b490bb --- /dev/null +++ b/packages/blocknumber/src/exceptions/index.ts @@ -0,0 +1 @@ +export * from "./invalidChain.js"; diff --git a/packages/blocknumber/src/exceptions/invalidChain.ts b/packages/blocknumber/src/exceptions/invalidChain.ts new file mode 100644 index 0000000..1e3a128 --- /dev/null +++ b/packages/blocknumber/src/exceptions/invalidChain.ts @@ -0,0 +1,7 @@ +export class InvalidChainId extends Error { + constructor(message: string) { + super(message); + + this.name = "InvalidChainId"; + } +} diff --git a/packages/blocknumber/src/utils/chainId.ts b/packages/blocknumber/src/utils/chainId.ts index ba6394d..4880fb2 100644 --- a/packages/blocknumber/src/utils/chainId.ts +++ b/packages/blocknumber/src/utils/chainId.ts @@ -1,5 +1,7 @@ // Based on https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md +import { InvalidChainId } from "../exceptions/invalidChain.js"; + type ChainNamespace = string; type ChainReference = string; @@ -11,14 +13,6 @@ interface ChainIdParams { const NAMESPACE_FORMAT = /^[-a-z0-9]{3,8}$/; const REFERENCE_FORMAT = /^[-_a-zA-Z0-9]{1,32}$/; -export class InvalidChainId extends Error { - constructor(message: string) { - super(message); - - this.name = "InvalidChainId"; - } -} - export class ChainId { private namespace: string; private reference: string; diff --git a/packages/blocknumber/test/utils/chainId.spec.ts b/packages/blocknumber/test/utils/chainId.spec.ts index 4e48c92..5187675 100644 --- a/packages/blocknumber/test/utils/chainId.spec.ts +++ b/packages/blocknumber/test/utils/chainId.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; -import { ChainId, InvalidChainId } from "../../src/utils/chainId.js"; +import { InvalidChainId } from "../../src/exceptions/invalidChain.js"; +import { ChainId } from "../../src/utils/chainId.js"; describe("ChainId", () => { describe("constructor", () => { From ecd91e970442bd0db0d235af9ce57f6931fa46df Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 23 Jul 2024 18:37:47 -0300 Subject: [PATCH 12/32] feat: search block by timestamp with binsearch --- package.json | 5 +- packages/blocknumber/package.json | 5 +- packages/blocknumber/src/exceptions/index.ts | 3 + .../src/exceptions/timestampNotFound.ts | 7 + .../src/exceptions/unsupportedBlockNumber.ts | 7 + .../exceptions/unsupportedBlockTimestamps.ts | 7 + .../src/providers/blockNumberProvider.ts | 11 + .../blocknumber/src/providers/evmProvider.ts | 146 +++++++ packages/blocknumber/src/utils/chainId.ts | 2 +- packages/blocknumber/src/utils/index.ts | 1 + packages/blocknumber/src/utils/logger.ts | 15 + .../test/providers/evmProvider.spec.ts | 173 ++++++++ pnpm-lock.yaml | 405 +++++++++++++++++- 13 files changed, 783 insertions(+), 4 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/timestampNotFound.ts create mode 100644 packages/blocknumber/src/exceptions/unsupportedBlockNumber.ts create mode 100644 packages/blocknumber/src/exceptions/unsupportedBlockTimestamps.ts create mode 100644 packages/blocknumber/src/providers/blockNumberProvider.ts create mode 100644 packages/blocknumber/src/providers/evmProvider.ts create mode 100644 packages/blocknumber/src/utils/logger.ts create mode 100644 packages/blocknumber/test/providers/evmProvider.spec.ts diff --git a/package.json b/package.json index abfffdc..1aff387 100644 --- a/package.json +++ b/package.json @@ -37,5 +37,8 @@ "*": "prettier --write --ignore-unknown", "*.js,*.ts": "eslint --fix" }, - "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903" + "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903", + "dependencies": { + "winston": "^3.13.1" + } } diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json index 90949f5..4e8339b 100644 --- a/packages/blocknumber/package.json +++ b/packages/blocknumber/package.json @@ -15,5 +15,8 @@ }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "dependencies": { + "viem": "2.17.10" + } } diff --git a/packages/blocknumber/src/exceptions/index.ts b/packages/blocknumber/src/exceptions/index.ts index 0b490bb..f6e8159 100644 --- a/packages/blocknumber/src/exceptions/index.ts +++ b/packages/blocknumber/src/exceptions/index.ts @@ -1 +1,4 @@ export * from "./invalidChain.js"; +export * from "./timestampNotFound.js"; +export * from "./unsupportedBlockNumber.js"; +export * from "./unsupportedBlockTimestamps.js"; diff --git a/packages/blocknumber/src/exceptions/timestampNotFound.ts b/packages/blocknumber/src/exceptions/timestampNotFound.ts new file mode 100644 index 0000000..ad3251b --- /dev/null +++ b/packages/blocknumber/src/exceptions/timestampNotFound.ts @@ -0,0 +1,7 @@ +export class TimestampNotFound extends Error { + constructor(timestamp: number | bigint) { + super(`No block was processed during ${timestamp}.`); + + this.name = "TimestampNotFound"; + } +} diff --git a/packages/blocknumber/src/exceptions/unsupportedBlockNumber.ts b/packages/blocknumber/src/exceptions/unsupportedBlockNumber.ts new file mode 100644 index 0000000..ace46c2 --- /dev/null +++ b/packages/blocknumber/src/exceptions/unsupportedBlockNumber.ts @@ -0,0 +1,7 @@ +export class UnsupportedBlockNumber extends Error { + constructor(timestamp: bigint) { + super(`Block with null block number at ${timestamp}`); + + this.name = "UnsupportedBlockNumber"; + } +} diff --git a/packages/blocknumber/src/exceptions/unsupportedBlockTimestamps.ts b/packages/blocknumber/src/exceptions/unsupportedBlockTimestamps.ts new file mode 100644 index 0000000..91c0f29 --- /dev/null +++ b/packages/blocknumber/src/exceptions/unsupportedBlockTimestamps.ts @@ -0,0 +1,7 @@ +export class UnsupportedBlockTimestamps extends Error { + constructor(timestamp: number | bigint) { + super(`Found multiple blocks at ${timestamp}.`); + + this.name = "UnsupportedBlockTimestamps"; + } +} diff --git a/packages/blocknumber/src/providers/blockNumberProvider.ts b/packages/blocknumber/src/providers/blockNumberProvider.ts new file mode 100644 index 0000000..778ada2 --- /dev/null +++ b/packages/blocknumber/src/providers/blockNumberProvider.ts @@ -0,0 +1,11 @@ +export interface BlockNumberProvider { + /** + * Get the epoch block number on a chain at a specific timestamp. + * + * @param timestamp UTC timestamp in ms since UNIX epoch + * @param url url of the chain data provider + * + * @returns the corresponding block number of a chain at a specific timestamp + */ + getEpochBlockNumber(timestamp: number, searchParams: unknown): Promise; +} diff --git a/packages/blocknumber/src/providers/evmProvider.ts b/packages/blocknumber/src/providers/evmProvider.ts new file mode 100644 index 0000000..3940a6f --- /dev/null +++ b/packages/blocknumber/src/providers/evmProvider.ts @@ -0,0 +1,146 @@ +import { Block, PublicClient } from "viem"; + +import { + TimestampNotFound, + UnsupportedBlockNumber, + UnsupportedBlockTimestamps, +} from "../exceptions/index.js"; +import logger from "../utils/logger.js"; +import { BlockNumberProvider } from "./blockNumberProvider.js"; + +const BINARY_SEARCH_DELTA_MULTIPLIER = 2n; + +type BlockWithNumber = Omit & { number: bigint }; + +export class EvmProvider implements BlockNumberProvider { + client: PublicClient; + + constructor(client: PublicClient) { + this.client = client; + } + + async getEpochBlockNumber( + timestamp: number, + searchParams: { blocksLookback: bigint }, + ): Promise { + const _timestamp = BigInt(timestamp); + const upperBoundBlock = await this.client.getBlock({ blockTag: "finalized" }); + + this.validateBlockNumber(upperBoundBlock); + + logger.info( + `Working with latest block (number: ${upperBoundBlock.number}, timestamp: ${upperBoundBlock.timestamp})...`, + ); + + if (_timestamp >= upperBoundBlock.timestamp) return upperBoundBlock.number; + + const lowerBoundBlock = await this.calculateLowerBoundBlock( + _timestamp, + upperBoundBlock, + searchParams.blocksLookback, + ); + + return this.searchTimestamp(_timestamp, { + fromBlock: lowerBoundBlock.number, + toBlock: upperBoundBlock.number, + }); + } + + private validateBlockNumber(block: Block): block is BlockWithNumber { + if (block.number === null) throw new UnsupportedBlockNumber(block.timestamp); + + return true; + } + + private async calculateLowerBoundBlock( + timestamp: bigint, + lastBlock: BlockWithNumber, + blocksLookback: bigint = 10_000n, + ) { + const estimatedBlockTime = await this.estimateBlockTime(lastBlock, blocksLookback); + const timestampDelta = lastBlock.timestamp - timestamp; + let candidateBlockNumber = lastBlock.number - timestampDelta / estimatedBlockTime; + + const baseStep = (lastBlock.number - candidateBlockNumber) * BINARY_SEARCH_DELTA_MULTIPLIER; + + logger.info("Calculating lower bound for binary search..."); + + let searchCount = 0n; + while (candidateBlockNumber >= 0) { + const candidate = await this.client.getBlock({ blockNumber: candidateBlockNumber }); + + if (candidate.timestamp < timestamp) { + logger.info(`Estimated lower bound at block ${candidate.number}.`); + + return candidate; + } + + searchCount++; + candidateBlockNumber = lastBlock.number - baseStep * 2n ** searchCount; + } + + const firstBlock = await this.client.getBlock({ blockNumber: 0n }); + + if (firstBlock.timestamp <= timestamp) { + return firstBlock; + } + + throw new TimestampNotFound(timestamp); + } + + private async estimateBlockTime(lastBlock: BlockWithNumber, blocksLookback: bigint) { + logger.info("Estimating block time..."); + + const pastBlock = await this.client.getBlock({ + blockNumber: lastBlock.number - BigInt(blocksLookback), + }); + + const estimatedBlockTime = (lastBlock.timestamp - pastBlock.timestamp) / blocksLookback; + + logger.info(`Estimated block time: ${estimatedBlockTime}.`); + + return estimatedBlockTime; + } + + private async searchTimestamp( + timestamp: bigint, + between: { fromBlock: bigint; toBlock: bigint }, + ) { + let currentBlockNumber: bigint; + let { fromBlock: low, toBlock: high } = between; + + logger.debug(`Starting block binary search for timestamp ${timestamp}...`); + + while (low <= high) { + currentBlockNumber = (high + low) / 2n; + + const currentBlock = await this.client.getBlock({ blockNumber: currentBlockNumber }); + const nextBlock = await this.client.getBlock({ blockNumber: currentBlockNumber + 1n }); + + logger.debug( + `Analyzing block number #${currentBlock.number} with timestamp ${currentBlock.timestamp}`, + ); + + // We do not support blocks with equal timestamps (nor non linear or non sequential chains). + // We could support same timestamps blocks by defining a criteria based on block height + // apart from their timestamps. + if (nextBlock.timestamp <= currentBlock.timestamp) + throw new UnsupportedBlockTimestamps(timestamp); + + const blockContainsTimestamp = + currentBlock.timestamp <= timestamp && nextBlock.timestamp > timestamp; + + if (blockContainsTimestamp) { + logger.debug(`Block #${currentBlock.number} contains timestamp.`); + + return currentBlock.number; + } else if (currentBlock.timestamp <= timestamp) { + low = currentBlockNumber + 1n; + } else { + high = currentBlockNumber - 1n; + } + } + + throw new TimestampNotFound(timestamp); + } +} diff --git a/packages/blocknumber/src/utils/chainId.ts b/packages/blocknumber/src/utils/chainId.ts index 4880fb2..2b20a16 100644 --- a/packages/blocknumber/src/utils/chainId.ts +++ b/packages/blocknumber/src/utils/chainId.ts @@ -33,7 +33,7 @@ export class ChainId { * Parses a CAIP-2 compliant string. * * @param chainId {string} a CAIP-2 compliant string - * @returns an object containing the namespace and the reference of the chain id + * @returns an object containing the namespace and the reference of the chain id. */ public static parse(chainId: string): ChainIdParams { const elements = chainId.split(":"); diff --git a/packages/blocknumber/src/utils/index.ts b/packages/blocknumber/src/utils/index.ts index 8e507d6..2697322 100644 --- a/packages/blocknumber/src/utils/index.ts +++ b/packages/blocknumber/src/utils/index.ts @@ -1 +1,2 @@ export * from "./chainId.js"; +export * from "./logger.js"; diff --git a/packages/blocknumber/src/utils/logger.ts b/packages/blocknumber/src/utils/logger.ts new file mode 100644 index 0000000..c18686d --- /dev/null +++ b/packages/blocknumber/src/utils/logger.ts @@ -0,0 +1,15 @@ +import winston from "winston"; + +const logger = winston.createLogger({ + level: "info", + format: winston.format.json(), + defaultMeta: { service: "blocknumber" }, + transports: [ + new winston.transports.Console({ + format: winston.format.simple(), + silent: process.env.NODE_ENV == "test", + }), + ], +}); + +export default logger; diff --git a/packages/blocknumber/test/providers/evmProvider.spec.ts b/packages/blocknumber/test/providers/evmProvider.spec.ts new file mode 100644 index 0000000..6842757 --- /dev/null +++ b/packages/blocknumber/test/providers/evmProvider.spec.ts @@ -0,0 +1,173 @@ +import { Block, createPublicClient, GetBlockParameters, http } from "viem"; +import { mainnet } from "viem/chains"; +import { describe, expect, it, vi } from "vitest"; + +import { TimestampNotFound } from "../../src/exceptions/timestampNotFound.js"; +import { UnsupportedBlockNumber } from "../../src/exceptions/unsupportedBlockNumber.js"; +import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; +import { EvmProvider } from "../../src/providers/evmProvider.js"; + +describe("EvmProvider", () => { + describe("getEpochBlockNumber", () => { + let evmProvider: EvmProvider; + + it("returns the first of two consecutive blocks when their timestamp contains the searched timestamp", async () => { + const blockNumber = 10n; + const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const endTimestamp = Date.UTC(2024, 1, 11, 0, 0, 0, 0); + const rpcProvider = mockRpcProvider(blockNumber, startTimestamp, endTimestamp); + + evmProvider = new EvmProvider(rpcProvider); + + const day5 = Date.UTC(2024, 1, 5, 2, 0, 0, 0); + const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5, { + blocksLookback: 2n, + }); + + expect(epochBlockNumber).toEqual(4n); + }); + + it("returns the block number when the timestamp is equal to block's timestamp", async () => { + const lastBlockNumber = 10n; + const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); + const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); + + evmProvider = new EvmProvider(rpcProvider); + + const exactDay5 = Date.UTC(2024, 1, 1, 0, 0, 5, 0); + const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5, { + blocksLookback: 2n, + }); + + expect(epochBlockNumber).toEqual(4n); + }); + + it("returns the last block if timestamp is after the block's timestamp", async () => { + const lastBlockNumber = 10n; + const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); + const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); + + evmProvider = new EvmProvider(rpcProvider); + + const futureTimestamp = Date.UTC(2025, 1, 1, 0, 0, 0, 0); + + const blockNumber = await evmProvider.getEpochBlockNumber(futureTimestamp, { + blocksLookback: 2n, + }); + + expect(blockNumber).toEqual(lastBlockNumber); + }); + + it("fails if the timestamp is before the first block", async () => { + const lastBlockNumber = 10n; + const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); + const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); + + evmProvider = new EvmProvider(rpcProvider); + + const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); + + expect( + evmProvider.getEpochBlockNumber(futureTimestamp, { + blocksLookback: 2n, + }), + ).rejects.toBeInstanceOf(TimestampNotFound); + }); + + it("fails when finding multiple blocks with the same timestamp", () => { + const timestamp = BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)); + const afterTimestamp = BigInt(Date.UTC(2024, 1, 2, 0, 0, 0, 0)); + const rpcProvider = mockRpcProviderBlocks([ + { number: 0n, timestamp: timestamp }, + { number: 1n, timestamp: timestamp }, + { number: 2n, timestamp: timestamp }, + { number: 3n, timestamp: timestamp }, + { number: 4n, timestamp: afterTimestamp }, + ]); + + evmProvider = new EvmProvider(rpcProvider); + + expect( + evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), + ).rejects.toBeInstanceOf(UnsupportedBlockTimestamps); + }); + + it("fails when finding a block with no number", () => { + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const rpcProvider = mockRpcProviderBlocks([ + { number: null, timestamp: BigInt(timestamp) }, + ]); + + evmProvider = new EvmProvider(rpcProvider); + + expect( + evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), + ).rejects.toBeInstanceOf(UnsupportedBlockNumber); + }); + + it("fails when the data provider fails", () => { + const client = createPublicClient({ chain: mainnet, transport: http() }); + + client.getBlock = vi.fn().mockRejectedValue(null); + + evmProvider = new EvmProvider(client); + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + + expect( + evmProvider.getEpochBlockNumber(timestamp, { blocksLookback: 2n }), + ).rejects.toBeDefined(); + }); + + it("fails when the chain did not reach to the block yet", async () => {}); + }); +}); + +function mockRpcProvider(lastBlock: bigint, startTimestamp: number, endTimestamp: number) { + const chainDuration = endTimestamp - startTimestamp; + const blockDuration = BigInt(chainDuration) / lastBlock; + + const rpcProvider = createPublicClient({ chain: mainnet, transport: http() }); + + rpcProvider.getBlock = vi + .fn() + .mockImplementation((args?: GetBlockParameters | undefined) => { + if (args?.blockTag == "finalized") { + return Promise.resolve({ + timestamp: BigInt(endTimestamp), + number: lastBlock, + }); + } else if (args?.blockNumber !== undefined) { + const blockNumber = args.blockNumber; + const blockTimestamp = BigInt(startTimestamp) + blockNumber * blockDuration; + + return Promise.resolve({ timestamp: blockTimestamp, number: blockNumber }); + } + + throw new Error("Unhandled getBlock mock case"); + }); + + return rpcProvider; +} + +function mockRpcProviderBlocks(blocks: Pick[]) { + const rpcProvider = createPublicClient({ chain: mainnet, transport: http() }); + + rpcProvider.getBlock = vi + .fn() + .mockImplementation((args?: GetBlockParameters | undefined) => { + if (args?.blockTag == "finalized") { + return Promise.resolve(blocks[blocks.length - 1]); + } else if (args?.blockNumber !== undefined) { + const blockNumber = Number(args.blockNumber); + + return Promise.resolve(blocks[blockNumber]); + } + + throw new Error("Unhandled getBlock mock case"); + }); + + return rpcProvider; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56d01ae..a78c010 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,10 @@ settings: importers: .: + dependencies: + winston: + specifier: ^3.13.1 + version: 3.13.1 devDependencies: "@commitlint/cli": specifier: 19.3.0 @@ -53,9 +57,19 @@ importers: specifier: 2.0.3 version: 2.0.3(@types/node@20.14.11) - packages/blocknumber: {} + packages/blocknumber: + dependencies: + viem: + specifier: ^2.17.10 + version: 2.17.10(typescript@5.5.3) packages: + "@adraffy/ens-normalize@1.10.0": + resolution: + { + integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==, + } + "@ampproject/remapping@2.3.0": resolution: { @@ -219,6 +233,13 @@ packages: integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, } + "@colors/colors@1.6.0": + resolution: + { + integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==, + } + engines: { node: ">=0.1.90" } + "@commitlint/cli@19.3.0": resolution: { @@ -339,6 +360,12 @@ packages: } engines: { node: ">=v18" } + "@dabh/diagnostics@2.0.3": + resolution: + { + integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==, + } + "@esbuild/aix-ppc64@0.21.5": resolution: { @@ -657,6 +684,19 @@ packages: integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==, } + "@noble/curves@1.4.0": + resolution: + { + integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==, + } + + "@noble/hashes@1.4.0": + resolution: + { + integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==, + } + engines: { node: ">= 16" } + "@nodelib/fs.scandir@2.1.5": resolution: { @@ -820,6 +860,24 @@ packages: cpu: [x64] os: [win32] + "@scure/base@1.1.7": + resolution: + { + integrity: sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==, + } + + "@scure/bip32@1.4.0": + resolution: + { + integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==, + } + + "@scure/bip39@1.3.0": + resolution: + { + integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==, + } + "@types/conventional-commits-parser@5.0.0": resolution: { @@ -838,6 +896,12 @@ packages: integrity: sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==, } + "@types/triple-beam@1.3.5": + resolution: + { + integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==, + } + "@typescript-eslint/eslint-plugin@7.16.1": resolution: { @@ -977,6 +1041,20 @@ packages: } hasBin: true + abitype@1.0.5: + resolution: + { + integrity: sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==, + } + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + acorn-jsx@5.3.2: resolution: { @@ -1073,6 +1151,12 @@ packages: } engines: { node: ">=12" } + async@3.2.5: + resolution: + { + integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==, + } + balanced-match@1.0.2: resolution: { @@ -1207,12 +1291,30 @@ packages: integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, } + color-string@1.9.1: + resolution: + { + integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==, + } + + color@3.2.1: + resolution: + { + integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==, + } + colorette@2.0.20: resolution: { integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, } + colorspace@1.1.4: + resolution: + { + integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==, + } + commander@12.1.0: resolution: { @@ -1373,6 +1475,12 @@ packages: integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, } + enabled@2.0.0: + resolution: + { + integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==, + } + env-paths@2.2.1: resolution: { @@ -1560,6 +1668,12 @@ packages: integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==, } + fecha@4.2.3: + resolution: + { + integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==, + } + file-entry-cache@6.0.1: resolution: { @@ -1601,6 +1715,12 @@ packages: integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==, } + fn.name@1.1.0: + resolution: + { + integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==, + } + foreground-child@3.2.1: resolution: { @@ -1814,6 +1934,12 @@ packages: integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, } + is-arrayish@0.3.2: + resolution: + { + integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==, + } + is-extglob@2.1.1: resolution: { @@ -1870,6 +1996,13 @@ packages: } engines: { node: ">=8" } + is-stream@2.0.1: + resolution: + { + integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, + } + engines: { node: ">=8" } + is-stream@3.0.0: resolution: { @@ -1890,6 +2023,14 @@ packages: integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, } + isows@1.0.4: + resolution: + { + integrity: sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==, + } + peerDependencies: + ws: "*" + istanbul-lib-coverage@3.2.2: resolution: { @@ -2009,6 +2150,12 @@ packages: integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, } + kuler@2.0.0: + resolution: + { + integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==, + } + levn@0.4.1: resolution: { @@ -2119,6 +2266,13 @@ packages: } engines: { node: ">=18" } + logform@2.6.1: + resolution: + { + integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==, + } + engines: { node: ">= 12.0.0" } + loupe@3.1.1: resolution: { @@ -2262,6 +2416,12 @@ packages: integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, } + one-time@1.0.0: + resolution: + { + integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==, + } + onetime@5.1.2: resolution: { @@ -2456,6 +2616,13 @@ packages: integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, } + readable-stream@3.6.2: + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, + } + engines: { node: ">= 6" } + require-directory@2.1.1: resolution: { @@ -2526,6 +2693,19 @@ packages: integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, } + safe-buffer@5.2.1: + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, + } + + safe-stable-stringify@2.4.3: + resolution: + { + integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==, + } + engines: { node: ">=10" } + semver@6.3.1: resolution: { @@ -2574,6 +2754,12 @@ packages: } engines: { node: ">=14" } + simple-swizzle@0.2.2: + resolution: + { + integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==, + } + slash@3.0.0: resolution: { @@ -2609,6 +2795,12 @@ packages: } engines: { node: ">= 10.x" } + stack-trace@0.0.10: + resolution: + { + integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==, + } + stackback@0.0.2: resolution: { @@ -2649,6 +2841,12 @@ packages: } engines: { node: ">=18" } + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, + } + strip-ansi@6.0.1: resolution: { @@ -2718,6 +2916,12 @@ packages: } engines: { node: ">=8" } + text-hex@1.0.0: + resolution: + { + integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==, + } + text-table@0.2.0: resolution: { @@ -2771,6 +2975,13 @@ packages: } engines: { node: ">=8.0" } + triple-beam@1.4.1: + resolution: + { + integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==, + } + engines: { node: ">= 14.0.0" } + ts-api-utils@1.3.0: resolution: { @@ -2891,6 +3102,23 @@ packages: integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, } + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + } + + viem@2.17.10: + resolution: + { + integrity: sha512-nubTeRBI3wsmYGemlg9PsbhunTSJSYUlq8VjzIQkbowCSPSd1bqzVeUU0qEOXsGtgRiFfBzXkkxFknZtBOAx/Q==, + } + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + vite-node@2.0.3: resolution: { @@ -2974,6 +3202,20 @@ packages: engines: { node: ">=8" } hasBin: true + winston-transport@4.7.1: + resolution: + { + integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==, + } + engines: { node: ">= 12.0.0" } + + winston@3.13.1: + resolution: + { + integrity: sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==, + } + engines: { node: ">= 12.0.0" } + word-wrap@1.2.5: resolution: { @@ -3008,6 +3250,21 @@ packages: integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, } + ws@8.17.1: + resolution: + { + integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==, + } + 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 + y18n@5.0.8: resolution: { @@ -3058,6 +3315,8 @@ packages: engines: { node: ">=12.20" } snapshots: + "@adraffy/ens-normalize@1.10.0": {} + "@ampproject/remapping@2.3.0": dependencies: "@jridgewell/gen-mapping": 0.3.5 @@ -3198,6 +3457,8 @@ snapshots: "@bcoe/v8-coverage@0.2.3": {} + "@colors/colors@1.6.0": {} + "@commitlint/cli@19.3.0(@types/node@20.14.11)(typescript@5.5.3)": dependencies: "@commitlint/format": 19.3.0 @@ -3309,6 +3570,12 @@ snapshots: "@types/conventional-commits-parser": 5.0.0 chalk: 5.3.0 + "@dabh/diagnostics@2.0.3": + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + "@esbuild/aix-ppc64@0.21.5": optional: true @@ -3453,6 +3720,12 @@ snapshots: "@jridgewell/resolve-uri": 3.1.2 "@jridgewell/sourcemap-codec": 1.5.0 + "@noble/curves@1.4.0": + dependencies: + "@noble/hashes": 1.4.0 + + "@noble/hashes@1.4.0": {} + "@nodelib/fs.scandir@2.1.5": dependencies: "@nodelib/fs.stat": 2.0.5 @@ -3518,6 +3791,19 @@ snapshots: "@rollup/rollup-win32-x64-msvc@4.18.1": optional: true + "@scure/base@1.1.7": {} + + "@scure/bip32@1.4.0": + dependencies: + "@noble/curves": 1.4.0 + "@noble/hashes": 1.4.0 + "@scure/base": 1.1.7 + + "@scure/bip39@1.3.0": + dependencies: + "@noble/hashes": 1.4.0 + "@scure/base": 1.1.7 + "@types/conventional-commits-parser@5.0.0": dependencies: "@types/node": 20.14.11 @@ -3528,6 +3814,8 @@ snapshots: dependencies: undici-types: 5.26.5 + "@types/triple-beam@1.3.5": {} + "@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)": dependencies: "@eslint-community/regexpp": 4.11.0 @@ -3668,6 +3956,10 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 + abitype@1.0.5(typescript@5.5.3): + optionalDependencies: + typescript: 5.5.3 + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 @@ -3712,6 +4004,8 @@ snapshots: assertion-error@2.0.1: {} + async@3.2.5: {} + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -3790,8 +4084,23 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + colorette@2.0.20: {} + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + commander@12.1.0: {} compare-func@2.0.0: @@ -3872,6 +4181,8 @@ snapshots: emoji-regex@9.2.2: {} + enabled@2.0.0: {} + env-paths@2.2.1: {} error-ex@1.3.2: @@ -4031,6 +4342,8 @@ snapshots: dependencies: reusify: 1.0.4 + fecha@4.2.3: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -4058,6 +4371,8 @@ snapshots: flatted@3.3.1: {} + fn.name@1.1.0: {} + foreground-child@3.2.1: dependencies: cross-spawn: 7.0.3 @@ -4163,6 +4478,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.2: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -4183,6 +4500,8 @@ snapshots: is-path-inside@3.0.3: {} + is-stream@2.0.1: {} + is-stream@3.0.0: {} is-text-path@2.0.0: @@ -4191,6 +4510,10 @@ snapshots: isexe@2.0.0: {} + isows@1.0.4(ws@8.17.1): + dependencies: + ws: 8.17.1 + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -4248,6 +4571,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kuler@2.0.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -4315,6 +4640,15 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 9.0.0 + logform@2.6.1: + dependencies: + "@colors/colors": 1.6.0 + "@types/triple-beam": 1.3.5 + fecha: 4.2.3 + ms: 2.1.2 + safe-stable-stringify: 2.4.3 + triple-beam: 1.4.1 + loupe@3.1.1: dependencies: get-func-name: 2.0.2 @@ -4382,6 +4716,10 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -4473,6 +4811,12 @@ snapshots: queue-microtask@1.2.3: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -4520,6 +4864,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.4.3: {} + semver@6.3.1: {} semver@7.6.3: {} @@ -4536,6 +4884,10 @@ snapshots: signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + slash@3.0.0: {} slice-ansi@5.0.0: @@ -4552,6 +4904,8 @@ snapshots: split2@4.2.0: {} + stack-trace@0.0.10: {} + stackback@0.0.2: {} std-env@3.7.0: {} @@ -4576,6 +4930,10 @@ snapshots: get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4613,6 +4971,8 @@ snapshots: text-extensions@2.4.0: {} + text-hex@1.0.0: {} + text-table@0.2.0: {} through@2.3.8: {} @@ -4631,6 +4991,8 @@ snapshots: dependencies: is-number: 7.0.0 + triple-beam@1.4.1: {} + ts-api-utils@1.3.0(typescript@5.5.3): dependencies: typescript: 5.5.3 @@ -4686,6 +5048,25 @@ snapshots: dependencies: punycode: 2.3.1 + util-deprecate@1.0.2: {} + + viem@2.17.10(typescript@5.5.3): + dependencies: + "@adraffy/ens-normalize": 1.10.0 + "@noble/curves": 1.4.0 + "@noble/hashes": 1.4.0 + "@scure/bip32": 1.4.0 + "@scure/bip39": 1.3.0 + abitype: 1.0.5(typescript@5.5.3) + isows: 1.0.4(ws@8.17.1) + ws: 8.17.1 + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + vite-node@2.0.3(@types/node@20.14.11): dependencies: cac: 6.7.14 @@ -4753,6 +5134,26 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + winston-transport@4.7.1: + dependencies: + logform: 2.6.1 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.13.1: + dependencies: + "@colors/colors": 1.6.0 + "@dabh/diagnostics": 2.0.3 + async: 3.2.5 + is-stream: 2.0.1 + logform: 2.6.1 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.7.1 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -4775,6 +5176,8 @@ snapshots: wrappy@1.0.2: {} + ws@8.17.1: {} + y18n@5.0.8: {} yallist@3.1.1: {} From 4a38c039601a59e118f685eba2fc37116a273add Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 24 Jul 2024 16:23:56 -0300 Subject: [PATCH 13/32] fix: fixed winston dependency version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1aff387..92defad 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,6 @@ }, "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903", "dependencies": { - "winston": "^3.13.1" + "winston": "3.13.1" } } From 711216dbb9f87484d510a762aea3540ce67c29cf Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 24 Jul 2024 16:56:21 -0300 Subject: [PATCH 14/32] refactor: rename evmProvider to evmBlockNumberProvider --- ...mProvider.ts => evmBlockNumberProvider.ts} | 2 +- ...spec.ts => evmBlockNumberProvider.spec.ts} | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) rename packages/blocknumber/src/providers/{evmProvider.ts => evmBlockNumberProvider.ts} (98%) rename packages/blocknumber/test/providers/{evmProvider.spec.ts => evmBlockNumberProvider.spec.ts} (91%) diff --git a/packages/blocknumber/src/providers/evmProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts similarity index 98% rename from packages/blocknumber/src/providers/evmProvider.ts rename to packages/blocknumber/src/providers/evmBlockNumberProvider.ts index 3940a6f..283d49f 100644 --- a/packages/blocknumber/src/providers/evmProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -12,7 +12,7 @@ const BINARY_SEARCH_DELTA_MULTIPLIER = 2n; type BlockWithNumber = Omit & { number: bigint }; -export class EvmProvider implements BlockNumberProvider { +export class EvmBlockNumberProvider implements BlockNumberProvider { client: PublicClient; constructor(client: PublicClient) { diff --git a/packages/blocknumber/test/providers/evmProvider.spec.ts b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts similarity index 91% rename from packages/blocknumber/test/providers/evmProvider.spec.ts rename to packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts index 6842757..6fd0184 100644 --- a/packages/blocknumber/test/providers/evmProvider.spec.ts +++ b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts @@ -5,11 +5,11 @@ import { describe, expect, it, vi } from "vitest"; import { TimestampNotFound } from "../../src/exceptions/timestampNotFound.js"; import { UnsupportedBlockNumber } from "../../src/exceptions/unsupportedBlockNumber.js"; import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; -import { EvmProvider } from "../../src/providers/evmProvider.js"; +import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider.js"; -describe("EvmProvider", () => { +describe("EvmBlockNumberProvider", () => { describe("getEpochBlockNumber", () => { - let evmProvider: EvmProvider; + let evmProvider: EvmBlockNumberProvider; it("returns the first of two consecutive blocks when their timestamp contains the searched timestamp", async () => { const blockNumber = 10n; @@ -17,7 +17,7 @@ describe("EvmProvider", () => { const endTimestamp = Date.UTC(2024, 1, 11, 0, 0, 0, 0); const rpcProvider = mockRpcProvider(blockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); const day5 = Date.UTC(2024, 1, 5, 2, 0, 0, 0); const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5, { @@ -33,7 +33,7 @@ describe("EvmProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); const exactDay5 = Date.UTC(2024, 1, 1, 0, 0, 5, 0); const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5, { @@ -49,7 +49,7 @@ describe("EvmProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); const futureTimestamp = Date.UTC(2025, 1, 1, 0, 0, 0, 0); @@ -66,7 +66,7 @@ describe("EvmProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); @@ -88,7 +88,7 @@ describe("EvmProvider", () => { { number: 4n, timestamp: afterTimestamp }, ]); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); expect( evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), @@ -101,7 +101,7 @@ describe("EvmProvider", () => { { number: null, timestamp: BigInt(timestamp) }, ]); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); expect( evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), @@ -113,7 +113,7 @@ describe("EvmProvider", () => { client.getBlock = vi.fn().mockRejectedValue(null); - evmProvider = new EvmProvider(client); + evmProvider = new EvmBlockNumberProvider(client); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); expect( From c7a9035c2617a09842d8eaaef81d7d237e05b51a Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 25 Jul 2024 13:32:53 -0300 Subject: [PATCH 15/32] refactor: client and search params in evm provider constructor --- .../src/exceptions/lastBlockEpoch.ts | 11 ++ .../src/exceptions/unexpectedSearchRange.ts | 9 ++ .../src/providers/blockNumberProvider.ts | 8 +- .../src/providers/evmBlockNumberProvider.ts | 128 +++++++++++++++--- .../providers/evmBlockNumberProvider.spec.ts | 60 ++++---- 5 files changed, 158 insertions(+), 58 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/lastBlockEpoch.ts create mode 100644 packages/blocknumber/src/exceptions/unexpectedSearchRange.ts diff --git a/packages/blocknumber/src/exceptions/lastBlockEpoch.ts b/packages/blocknumber/src/exceptions/lastBlockEpoch.ts new file mode 100644 index 0000000..40ff8bd --- /dev/null +++ b/packages/blocknumber/src/exceptions/lastBlockEpoch.ts @@ -0,0 +1,11 @@ +import { Block } from "viem"; + +export class LastBlockEpoch extends Error { + constructor(block: Block) { + super( + `Cannot specify the start of the epoch with the last block only (number: ${block.number}), wait for it to be finalized.`, + ); + + this.name = "LastBlockEpoch"; + } +} diff --git a/packages/blocknumber/src/exceptions/unexpectedSearchRange.ts b/packages/blocknumber/src/exceptions/unexpectedSearchRange.ts new file mode 100644 index 0000000..1811c18 --- /dev/null +++ b/packages/blocknumber/src/exceptions/unexpectedSearchRange.ts @@ -0,0 +1,9 @@ +export class UnexpectedSearchRange extends Error { + constructor(low: bigint, high: bigint) { + super( + `Lower bound of search range (${low}) must be less than or equal to upper bound (${high})`, + ); + + this.name = "UnexpectedSearchRange"; + } +} diff --git a/packages/blocknumber/src/providers/blockNumberProvider.ts b/packages/blocknumber/src/providers/blockNumberProvider.ts index 778ada2..0b3b74e 100644 --- a/packages/blocknumber/src/providers/blockNumberProvider.ts +++ b/packages/blocknumber/src/providers/blockNumberProvider.ts @@ -1,11 +1,13 @@ export interface BlockNumberProvider { /** - * Get the epoch block number on a chain at a specific timestamp. + * Get the block number corresponding to the beginning of the epoch. + * + * The input timestamp falls between the timestamps of the found block and + * the immediately following block. * * @param timestamp UTC timestamp in ms since UNIX epoch - * @param url url of the chain data provider * * @returns the corresponding block number of a chain at a specific timestamp */ - getEpochBlockNumber(timestamp: number, searchParams: unknown): Promise; + getEpochBlockNumber(timestamp: number): Promise; } diff --git a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts index 283d49f..9568f96 100644 --- a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -1,29 +1,63 @@ -import { Block, PublicClient } from "viem"; +import { Block, fromBlobs, PublicClient } from "viem"; import { TimestampNotFound, UnsupportedBlockNumber, UnsupportedBlockTimestamps, } from "../exceptions/index.js"; +import { LastBlockEpoch } from "../exceptions/lastBlockEpoch.js"; +import { UnexpectedSearchRange } from "../exceptions/unexpectedSearchRange.js"; import logger from "../utils/logger.js"; import { BlockNumberProvider } from "./blockNumberProvider.js"; +const BINARY_SEARCH_BLOCKS_LOOKBACK = 10_000n; const BINARY_SEARCH_DELTA_MULTIPLIER = 2n; type BlockWithNumber = Omit & { number: bigint }; -export class EvmBlockNumberProvider implements BlockNumberProvider { - client: PublicClient; +interface SearchConfig { + /** + * Indicates how many blocks should be used for estimating the chain's block time + */ + blocksLookback: bigint; + + /** + * Multiplier to apply to the step, used while scanning blocks backwards, to find a + * lower bound block. + */ + deltaMultiplier: bigint; +} - constructor(client: PublicClient) { +export class EvmBlockNumberProvider implements BlockNumberProvider { + private client: PublicClient; + private searchConfig: SearchConfig; + private firstBlock: Block; + + /** + * Creates a new instance of PublicClient. + * + * @param client the viem client to use for EVM compatible RPC node calls. + * @param searchConfig.blocksLookback amount of blocks that should be used for + * estimating the chain's block time. Defaults to 10.000 blocks. + * @param searchConfig.deltaMultiplier multiplier to apply to the step, used + * while scanning blocks backwards during lower bound search. Defaults to 2. + */ + constructor( + client: PublicClient, + searchConfig: { blocksLookback?: bigint; deltaMultiplier?: bigint }, + ) { this.client = client; + this.searchConfig = { + blocksLookback: searchConfig.blocksLookback ?? BINARY_SEARCH_BLOCKS_LOOKBACK, + deltaMultiplier: searchConfig.deltaMultiplier ?? BINARY_SEARCH_DELTA_MULTIPLIER, + }; } - async getEpochBlockNumber( - timestamp: number, - searchParams: { blocksLookback: bigint }, - ): Promise { + async getEpochBlockNumber(timestamp: number): Promise { + // An optimized binary search is used to look for the epoch block. const _timestamp = BigInt(timestamp); + + // The EBO agent looks only for finalized blocks to avoid handling reorgs const upperBoundBlock = await this.client.getBlock({ blockTag: "finalized" }); this.validateBlockNumber(upperBoundBlock); @@ -32,36 +66,71 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { `Working with latest block (number: ${upperBoundBlock.number}, timestamp: ${upperBoundBlock.timestamp})...`, ); - if (_timestamp >= upperBoundBlock.timestamp) return upperBoundBlock.number; + const firstBlock = await this.getFirstBlock(); - const lowerBoundBlock = await this.calculateLowerBoundBlock( - _timestamp, - upperBoundBlock, - searchParams.blocksLookback, - ); + if (_timestamp < firstBlock.timestamp) throw new TimestampNotFound(_timestamp); + if (_timestamp >= upperBoundBlock.timestamp) throw new LastBlockEpoch(upperBoundBlock); + // Reduces the search space by estimating a lower bound for the binary search. + // + // Performing a binary search between block 0 and last block is not efficient. + const lowerBoundBlock = await this.calculateLowerBoundBlock(_timestamp, upperBoundBlock); + + // Searches for the timestamp with a binary search return this.searchTimestamp(_timestamp, { fromBlock: lowerBoundBlock.number, toBlock: upperBoundBlock.number, }); } + /** + * Fetches and caches the first block. Cached block will be returned if the cache is hit. + * + * @returns the chain's first block + */ + private async getFirstBlock(): Promise { + if (this.firstBlock !== undefined) return this.firstBlock; + + this.firstBlock = await this.client.getBlock({ blockNumber: 0n }); + + return this.firstBlock; + } + + /** + * Validates that a block contains a non-null number + * + * @param block viem block + * @throws {UnsupportedBlockNumber} when block contains a null number + * @returns true if the block contains a non-null number + */ private validateBlockNumber(block: Block): block is BlockWithNumber { if (block.number === null) throw new UnsupportedBlockNumber(block.timestamp); return true; } - private async calculateLowerBoundBlock( - timestamp: bigint, - lastBlock: BlockWithNumber, - blocksLookback: bigint = 10_000n, - ) { + /** + * Searches for an efficient lower bound to run the binary search, leveraging that + * the epoch start tends to be relatively near the last block. + * + * The amount of blocks to look back from the last block is estimated, using an + * estimated block-time based on the last `searchConfig.blocksLookback` blocks. + * + * Until a block with a timestamp before the input timestamp is found, backward + * exponentially grown steps are performed. + * + * @param timestamp timestamp of the epoch start + * @param lastBlock last block of the chain + * @returns an optimized lower bound for a binary search space + */ + private async calculateLowerBoundBlock(timestamp: bigint, lastBlock: BlockWithNumber) { + const { blocksLookback, deltaMultiplier } = this.searchConfig; + const estimatedBlockTime = await this.estimateBlockTime(lastBlock, blocksLookback); const timestampDelta = lastBlock.timestamp - timestamp; let candidateBlockNumber = lastBlock.number - timestampDelta / estimatedBlockTime; - const baseStep = (lastBlock.number - candidateBlockNumber) * BINARY_SEARCH_DELTA_MULTIPLIER; + const baseStep = (lastBlock.number - candidateBlockNumber) * deltaMultiplier; logger.info("Calculating lower bound for binary search..."); @@ -88,6 +157,13 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { throw new TimestampNotFound(timestamp); } + /** + * Estimates the chain's block time based on the last `blocksLookback` blocks. + * + * @param lastBlock last chain block + * @param blocksLookback amount of blocks to look back + * @returns the estimated block time + */ private async estimateBlockTime(lastBlock: BlockWithNumber, blocksLookback: bigint) { logger.info("Estimating block time..."); @@ -102,6 +178,16 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { return estimatedBlockTime; } + /** + * Performs a binary search in the specified block range to find the block corresponding to a timestamp. + * + * @param timestamp timestamp to find the block for + * @param between blocks search space + * @throws {UnsupportedBlockTimestamps} when two consecutive blocks with the same timestamp are found + * during the search. These chains are not supported at the moment. + * @throws {TimestampNotFound} when the search is finished and no block includes the searched timestamp + * @returns the block number + */ private async searchTimestamp( timestamp: bigint, between: { fromBlock: bigint; toBlock: bigint }, @@ -109,6 +195,8 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { let currentBlockNumber: bigint; let { fromBlock: low, toBlock: high } = between; + if (low > high) throw new UnexpectedSearchRange(low, high); + logger.debug(`Starting block binary search for timestamp ${timestamp}...`); while (low <= high) { diff --git a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts index 6fd0184..968a0ec 100644 --- a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts +++ b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts @@ -2,6 +2,7 @@ import { Block, createPublicClient, GetBlockParameters, http } from "viem"; import { mainnet } from "viem/chains"; import { describe, expect, it, vi } from "vitest"; +import { LastBlockEpoch } from "../../src/exceptions/lastBlockEpoch.js"; import { TimestampNotFound } from "../../src/exceptions/timestampNotFound.js"; import { UnsupportedBlockNumber } from "../../src/exceptions/unsupportedBlockNumber.js"; import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; @@ -9,6 +10,7 @@ import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvid describe("EvmBlockNumberProvider", () => { describe("getEpochBlockNumber", () => { + const searchConfig = { blocksLookback: 2n, deltaMultiplier: 2n }; let evmProvider: EvmBlockNumberProvider; it("returns the first of two consecutive blocks when their timestamp contains the searched timestamp", async () => { @@ -17,12 +19,10 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 11, 0, 0, 0, 0); const rpcProvider = mockRpcProvider(blockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); const day5 = Date.UTC(2024, 1, 5, 2, 0, 0, 0); - const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5, { - blocksLookback: 2n, - }); + const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5); expect(epochBlockNumber).toEqual(4n); }); @@ -33,31 +33,27 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); const exactDay5 = Date.UTC(2024, 1, 1, 0, 0, 5, 0); - const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5, { - blocksLookback: 2n, - }); + const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5); expect(epochBlockNumber).toEqual(4n); }); - it("returns the last block if timestamp is after the block's timestamp", async () => { + it("throws if the search timestamp is after the last block's timestamp", async () => { const lastBlockNumber = 10n; const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); const futureTimestamp = Date.UTC(2025, 1, 1, 0, 0, 0, 0); - const blockNumber = await evmProvider.getEpochBlockNumber(futureTimestamp, { - blocksLookback: 2n, - }); - - expect(blockNumber).toEqual(lastBlockNumber); + expect(evmProvider.getEpochBlockNumber(futureTimestamp)).rejects.toBeInstanceOf( + LastBlockEpoch, + ); }); it("fails if the timestamp is before the first block", async () => { @@ -66,15 +62,13 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); - expect( - evmProvider.getEpochBlockNumber(futureTimestamp, { - blocksLookback: 2n, - }), - ).rejects.toBeInstanceOf(TimestampNotFound); + expect(evmProvider.getEpochBlockNumber(futureTimestamp)).rejects.toBeInstanceOf( + TimestampNotFound, + ); }); it("fails when finding multiple blocks with the same timestamp", () => { @@ -88,11 +82,11 @@ describe("EvmBlockNumberProvider", () => { { number: 4n, timestamp: afterTimestamp }, ]); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); - expect( - evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), - ).rejects.toBeInstanceOf(UnsupportedBlockTimestamps); + expect(evmProvider.getEpochBlockNumber(Number(timestamp))).rejects.toBeInstanceOf( + UnsupportedBlockTimestamps, + ); }); it("fails when finding a block with no number", () => { @@ -101,11 +95,11 @@ describe("EvmBlockNumberProvider", () => { { number: null, timestamp: BigInt(timestamp) }, ]); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); - expect( - evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), - ).rejects.toBeInstanceOf(UnsupportedBlockNumber); + expect(evmProvider.getEpochBlockNumber(Number(timestamp))).rejects.toBeInstanceOf( + UnsupportedBlockNumber, + ); }); it("fails when the data provider fails", () => { @@ -113,15 +107,11 @@ describe("EvmBlockNumberProvider", () => { client.getBlock = vi.fn().mockRejectedValue(null); - evmProvider = new EvmBlockNumberProvider(client); + evmProvider = new EvmBlockNumberProvider(client, searchConfig); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); - expect( - evmProvider.getEpochBlockNumber(timestamp, { blocksLookback: 2n }), - ).rejects.toBeDefined(); + expect(evmProvider.getEpochBlockNumber(timestamp)).rejects.toBeDefined(); }); - - it("fails when the chain did not reach to the block yet", async () => {}); }); }); From 5ad13690dc0149f27f7644d39a8339b0bf486477 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 25 Jul 2024 13:43:36 -0300 Subject: [PATCH 16/32] fix: pnpm dependecies mismatch --- pnpm-lock.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a78c010..86ac1c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,7 +8,7 @@ importers: .: dependencies: winston: - specifier: ^3.13.1 + specifier: 3.13.1 version: 3.13.1 devDependencies: "@commitlint/cli": @@ -60,7 +60,7 @@ importers: packages/blocknumber: dependencies: viem: - specifier: ^2.17.10 + specifier: 2.17.10 version: 2.17.10(typescript@5.5.3) packages: From 6875aeacb95d8dc462b876e72696980686287eed Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 25 Jul 2024 16:26:53 -0300 Subject: [PATCH 17/32] fix: throw InvalidTimestamp for timestamps prior first block --- packages/blocknumber/src/exceptions/index.ts | 1 + .../blocknumber/src/exceptions/invalidTimestamp.ts | 7 +++++++ .../src/providers/evmBlockNumberProvider.ts | 3 ++- .../test/providers/evmBlockNumberProvider.spec.ts | 12 +++++++----- 4 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/invalidTimestamp.ts diff --git a/packages/blocknumber/src/exceptions/index.ts b/packages/blocknumber/src/exceptions/index.ts index a9718bc..0354a89 100644 --- a/packages/blocknumber/src/exceptions/index.ts +++ b/packages/blocknumber/src/exceptions/index.ts @@ -1,4 +1,5 @@ export * from "./invalidChain.js"; +export * from "./invalidTimestamp.js"; export * from "./lastBlockEpoch.js"; export * from "./timestampNotFound.js"; export * from "./unexpectedSearchRange.js"; diff --git a/packages/blocknumber/src/exceptions/invalidTimestamp.ts b/packages/blocknumber/src/exceptions/invalidTimestamp.ts new file mode 100644 index 0000000..1eac699 --- /dev/null +++ b/packages/blocknumber/src/exceptions/invalidTimestamp.ts @@ -0,0 +1,7 @@ +export class InvalidTimestamp extends Error { + constructor(timestamp: number | bigint) { + super(`Timestamp ${timestamp} is prior the timestamp of the first block.`); + + this.name = "InvalidTimestamp"; + } +} diff --git a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts index 7dd2c08..8bbf88f 100644 --- a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -1,6 +1,7 @@ import { Block, PublicClient } from "viem"; import { + InvalidTimestamp, LastBlockEpoch, TimestampNotFound, UnexpectedSearchRange, @@ -69,7 +70,7 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { const firstBlock = await this.getFirstBlock(); - if (_timestamp < firstBlock.timestamp) throw new TimestampNotFound(_timestamp); + if (_timestamp < firstBlock.timestamp) throw new InvalidTimestamp(_timestamp); if (_timestamp >= upperBoundBlock.timestamp) throw new LastBlockEpoch(upperBoundBlock); // Reduces the search space by estimating a lower bound for the binary search. diff --git a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts index 968a0ec..e2ed814 100644 --- a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts +++ b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts @@ -2,10 +2,12 @@ import { Block, createPublicClient, GetBlockParameters, http } from "viem"; import { mainnet } from "viem/chains"; import { describe, expect, it, vi } from "vitest"; -import { LastBlockEpoch } from "../../src/exceptions/lastBlockEpoch.js"; -import { TimestampNotFound } from "../../src/exceptions/timestampNotFound.js"; -import { UnsupportedBlockNumber } from "../../src/exceptions/unsupportedBlockNumber.js"; -import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; +import { + InvalidTimestamp, + LastBlockEpoch, + UnsupportedBlockNumber, + UnsupportedBlockTimestamps, +} from "../../src/exceptions/index.js"; import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider.js"; describe("EvmBlockNumberProvider", () => { @@ -67,7 +69,7 @@ describe("EvmBlockNumberProvider", () => { const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); expect(evmProvider.getEpochBlockNumber(futureTimestamp)).rejects.toBeInstanceOf( - TimestampNotFound, + InvalidTimestamp, ); }); From 80da90d58f0d62fddfdfc6f817240a533a0a3221 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 25 Jul 2024 16:36:56 -0300 Subject: [PATCH 18/32] chore: remove dummy classes --- packages/blocknumber/src/helloWorld.spec.ts | 11 ----------- packages/blocknumber/src/helloWorld.ts | 3 --- 2 files changed, 14 deletions(-) delete mode 100644 packages/blocknumber/src/helloWorld.spec.ts delete mode 100644 packages/blocknumber/src/helloWorld.ts diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts deleted file mode 100644 index bdcee5e..0000000 --- a/packages/blocknumber/src/helloWorld.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import foo from "./helloWorld.js"; - -describe("test", () => { - it("pass", () => { - const result = foo(); - - expect(result).toBe("bar"); - }); -}); diff --git a/packages/blocknumber/src/helloWorld.ts b/packages/blocknumber/src/helloWorld.ts deleted file mode 100644 index 0838c22..0000000 --- a/packages/blocknumber/src/helloWorld.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function foo() { - return "bar"; -} From 9a6007613f4190baee3118428bb220aa1fdc027d Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 26 Jul 2024 16:37:27 -0300 Subject: [PATCH 19/32] refactor: move and redefine caip2 chain id --- .../src/utils/{chainId.ts => caip/caip2.ts} | 45 +++++-------------- packages/blocknumber/src/utils/caip/index.ts | 1 + .../blocknumber/test/utils/caip/caip.spec.ts | 32 +++++++++++++ .../blocknumber/test/utils/chainId.spec.ts | 41 ----------------- 4 files changed, 45 insertions(+), 74 deletions(-) rename packages/blocknumber/src/utils/{chainId.ts => caip/caip2.ts} (52%) create mode 100644 packages/blocknumber/src/utils/caip/index.ts create mode 100644 packages/blocknumber/test/utils/caip/caip.spec.ts delete mode 100644 packages/blocknumber/test/utils/chainId.spec.ts diff --git a/packages/blocknumber/src/utils/chainId.ts b/packages/blocknumber/src/utils/caip/caip2.ts similarity index 52% rename from packages/blocknumber/src/utils/chainId.ts rename to packages/blocknumber/src/utils/caip/caip2.ts index 4880fb2..f2a5a09 100644 --- a/packages/blocknumber/src/utils/chainId.ts +++ b/packages/blocknumber/src/utils/caip/caip2.ts @@ -1,41 +1,27 @@ // Based on https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md -import { InvalidChainId } from "../exceptions/invalidChain.js"; - -type ChainNamespace = string; -type ChainReference = string; - -interface ChainIdParams { - namespace: ChainNamespace; - reference: ChainReference; -} +import { InvalidChainId } from "../../exceptions/invalidChain.js"; +import { Caip2ChainId } from "../../types.js"; const NAMESPACE_FORMAT = /^[-a-z0-9]{3,8}$/; const REFERENCE_FORMAT = /^[-_a-zA-Z0-9]{1,32}$/; -export class ChainId { - private namespace: string; - private reference: string; - - /** - * Creates a validated CAIP-2 compliant chain ID. - * - * @param chainId a CAIP-2 compliant string. - */ - constructor(chainId: string) { - const params = ChainId.parse(chainId); +type SupportedChains = "mainnet" | "polygon" | "arbitrum"; - this.namespace = params.namespace; - this.reference = params.reference; - } +export const chains = { + mainnet: "eip155:1", + polygon: "eip155:137", + arbitrum: "eip155:42161", +} as Record; +export class Caip2 { /** * Parses a CAIP-2 compliant string. * * @param chainId {string} a CAIP-2 compliant string - * @returns an object containing the namespace and the reference of the chain id + * @returns the CAIP-2 validated chain id string */ - public static parse(chainId: string): ChainIdParams { + public static validateChainId(chainId: string): chainId is Caip2ChainId { const elements = chainId.split(":"); if (elements.length !== 2) { @@ -54,13 +40,6 @@ export class ChainId { const isValidReference = REFERENCE_FORMAT.test(reference); if (!isValidReference) throw new InvalidChainId("Chain ID reference is not valid."); - return { - namespace, - reference, - }; - } - - public toString() { - return `${this.namespace}:${this.reference}`; + return true; } } diff --git a/packages/blocknumber/src/utils/caip/index.ts b/packages/blocknumber/src/utils/caip/index.ts new file mode 100644 index 0000000..a8a261f --- /dev/null +++ b/packages/blocknumber/src/utils/caip/index.ts @@ -0,0 +1 @@ +export * from "./caip2.js"; diff --git a/packages/blocknumber/test/utils/caip/caip.spec.ts b/packages/blocknumber/test/utils/caip/caip.spec.ts new file mode 100644 index 0000000..d444337 --- /dev/null +++ b/packages/blocknumber/test/utils/caip/caip.spec.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; + +import { InvalidChainId } from "../../../src/exceptions/invalidChain.js"; +import { Caip2 } from "../../../src/utils/caip/index.js"; + +describe("Caip2", () => { + describe("validateChainId", () => { + it("validates a CAIP-2 compliant chain id", () => { + const isValid = Caip2.validateChainId("eip155:1"); + + expect(isValid).toBe(true); + }); + + it("fails when input chain id is not caip-2 compliant", () => { + const chainId = "foobar"; + + expect(() => Caip2.validateChainId(chainId)).toThrowError(InvalidChainId); + }); + + it("fails when input namespace is not caip-2 compliant", () => { + const chainId = "f:1"; + + expect(() => Caip2.validateChainId(chainId)).toThrowError(InvalidChainId); + }); + + it("fails when input reference is not caip-2 compliant", () => { + const chainId = "foo:!nval!d"; + + expect(() => Caip2.validateChainId(chainId)).toThrowError(InvalidChainId); + }); + }); +}); diff --git a/packages/blocknumber/test/utils/chainId.spec.ts b/packages/blocknumber/test/utils/chainId.spec.ts deleted file mode 100644 index 5187675..0000000 --- a/packages/blocknumber/test/utils/chainId.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { InvalidChainId } from "../../src/exceptions/invalidChain.js"; -import { ChainId } from "../../src/utils/chainId.js"; - -describe("ChainId", () => { - describe("constructor", () => { - it("creates a valid chain id instance", () => { - const chainId = new ChainId("eip155:1"); - - expect(chainId).toBeInstanceOf(ChainId); - }); - - it("fails when input chain id is not caip-2 compliant", () => { - const chainId = "foobar"; - - expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); - }); - - it("fails when input namespace is not caip-2 compliant", () => { - const chainId = "f:1"; - - expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); - }); - - it("fails when input reference is not caip-2 compliant", () => { - const chainId = "foo:!nval!d"; - - expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); - }); - }); - - describe("toString", () => { - it("returns a CAIP-2 compliant string", () => { - const ethChainId = "eip155:1"; - const chainId = new ChainId(ethChainId); - - expect(chainId.toString()).toEqual(ethChainId); - }); - }); -}); From 60a57e1eea8f099f954a6146fb856b4200ad0327 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 26 Jul 2024 16:38:55 -0300 Subject: [PATCH 20/32] feat: implements BlockNumberService --- .../src/exceptions/chainWithoutProvider.ts | 9 + .../src/exceptions/emptyRpcUrls.ts | 7 + packages/blocknumber/src/exceptions/index.ts | 3 + .../src/exceptions/unsupportedChain.ts | 9 + .../src/providers/evmBlockNumberProvider.ts | 6 +- .../src/services/blockNumberService.ts | 79 +++++++++ packages/blocknumber/src/services/index.ts | 1 + packages/blocknumber/src/types.ts | 1 + packages/blocknumber/src/utils/index.ts | 2 +- .../test/services/blockNumberService.spec.ts | 164 ++++++++++++++++++ 10 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/chainWithoutProvider.ts create mode 100644 packages/blocknumber/src/exceptions/emptyRpcUrls.ts create mode 100644 packages/blocknumber/src/exceptions/unsupportedChain.ts create mode 100644 packages/blocknumber/src/services/blockNumberService.ts create mode 100644 packages/blocknumber/src/services/index.ts create mode 100644 packages/blocknumber/src/types.ts create mode 100644 packages/blocknumber/test/services/blockNumberService.spec.ts diff --git a/packages/blocknumber/src/exceptions/chainWithoutProvider.ts b/packages/blocknumber/src/exceptions/chainWithoutProvider.ts new file mode 100644 index 0000000..4664c2e --- /dev/null +++ b/packages/blocknumber/src/exceptions/chainWithoutProvider.ts @@ -0,0 +1,9 @@ +import { Caip2ChainId } from "../types.js"; + +export class ChainWithoutProvider extends Error { + constructor(chainId: Caip2ChainId) { + super(`Chain ${chainId} has no provider defined.`); + + this.name = "ChainWithoutProvider"; + } +} diff --git a/packages/blocknumber/src/exceptions/emptyRpcUrls.ts b/packages/blocknumber/src/exceptions/emptyRpcUrls.ts new file mode 100644 index 0000000..90d749b --- /dev/null +++ b/packages/blocknumber/src/exceptions/emptyRpcUrls.ts @@ -0,0 +1,7 @@ +export class EmptyRpcUrls extends Error { + constructor() { + super(`At least one chain with its RPC endpoint must be defined.`); + + this.name = "EmptyRpcUrls"; + } +} diff --git a/packages/blocknumber/src/exceptions/index.ts b/packages/blocknumber/src/exceptions/index.ts index 0354a89..db14d30 100644 --- a/packages/blocknumber/src/exceptions/index.ts +++ b/packages/blocknumber/src/exceptions/index.ts @@ -1,3 +1,5 @@ +export * from "./chainWithoutProvider.js"; +export * from "./emptyRpcUrls.js"; export * from "./invalidChain.js"; export * from "./invalidTimestamp.js"; export * from "./lastBlockEpoch.js"; @@ -5,3 +7,4 @@ export * from "./timestampNotFound.js"; export * from "./unexpectedSearchRange.js"; export * from "./unsupportedBlockNumber.js"; export * from "./unsupportedBlockTimestamps.js"; +export * from "./unsupportedChain.js"; diff --git a/packages/blocknumber/src/exceptions/unsupportedChain.ts b/packages/blocknumber/src/exceptions/unsupportedChain.ts new file mode 100644 index 0000000..8a53901 --- /dev/null +++ b/packages/blocknumber/src/exceptions/unsupportedChain.ts @@ -0,0 +1,9 @@ +import { Caip2ChainId } from "../types.js"; + +export class UnsupportedChain extends Error { + constructor(chainId: Caip2ChainId) { + super(`Chain ${chainId} is not supported.`); + + this.name = "UnsupportedChain"; + } +} diff --git a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts index 8bbf88f..98fd8c0 100644 --- a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -1,4 +1,4 @@ -import { Block, PublicClient } from "viem"; +import { Block, FallbackTransport, HttpTransport, PublicClient } from "viem"; import { InvalidTimestamp, @@ -30,7 +30,7 @@ interface SearchConfig { } export class EvmBlockNumberProvider implements BlockNumberProvider { - private client: PublicClient; + private client: PublicClient>; private searchConfig: SearchConfig; private firstBlock: Block | null; @@ -44,7 +44,7 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { * while scanning blocks backwards during lower bound search. Defaults to 2. */ constructor( - client: PublicClient, + client: PublicClient>, searchConfig: { blocksLookback?: bigint; deltaMultiplier?: bigint }, ) { this.client = client; diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts new file mode 100644 index 0000000..05d05ff --- /dev/null +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -0,0 +1,79 @@ +import { + createPublicClient, + fallback, + FallbackTransport, + http, + HttpTransport, + PublicClient, +} from "viem"; + +import { ChainWithoutProvider, EmptyRpcUrls, UnsupportedChain } from "../exceptions/index.js"; +import { BlockNumberProvider } from "../providers/blockNumberProvider.js"; +import { EvmBlockNumberProvider } from "../providers/evmBlockNumberProvider.js"; +import { Caip2ChainId } from "../types.js"; + +type RpcUrl = NonNullable[0]>; + +export class BlockNumberService { + private blockNumberProviders: Map; + + constructor(chainRpcUrls: Map) { + this.blockNumberProviders = this.buildBlockNumberProviders(chainRpcUrls); + } + + public async getEpochBlockNumbers(timestamp: number, chains: Caip2ChainId[]) { + const epochBlockNumbers = await Promise.all( + chains.map(async (chainId) => { + const provider = this.blockNumberProviders.get(chainId); + + if (!provider) throw new ChainWithoutProvider(chainId); + + const blockNumber = await provider.getEpochBlockNumber(timestamp); + + return [chainId, blockNumber] as [Caip2ChainId, bigint]; + }), + ); + + const e = epochBlockNumbers.filter( + (entry): entry is [Caip2ChainId, bigint] => entry !== null, + ); + + return new Map(e); + } + + private buildBlockNumberProviders(chainRpcUrls: Map) { + if (chainRpcUrls.size == 0) throw new EmptyRpcUrls(); + + const providers = new Map(); + + for (const [chainId, urls] of chainRpcUrls) { + const client = createPublicClient({ + transport: fallback(urls.map((url) => http(url))), + }); + + const provider = BlockNumberService.buildProvider(chainId, client); + + if (!provider) throw new ChainWithoutProvider(chainId); + + providers.set(chainId, provider); + } + + return providers; + } + + public static buildProvider( + chainId: Caip2ChainId, + client: PublicClient>, + ) { + switch (chainId) { + case "eip155:1": + return new EvmBlockNumberProvider(client, { + blocksLookback: 10_000n, + deltaMultiplier: 2n, + }); + + default: + throw new UnsupportedChain(chainId); + } + } +} diff --git a/packages/blocknumber/src/services/index.ts b/packages/blocknumber/src/services/index.ts new file mode 100644 index 0000000..1c7f9e1 --- /dev/null +++ b/packages/blocknumber/src/services/index.ts @@ -0,0 +1 @@ +export * from "./blockNumberService.js"; diff --git a/packages/blocknumber/src/types.ts b/packages/blocknumber/src/types.ts new file mode 100644 index 0000000..9c11cf1 --- /dev/null +++ b/packages/blocknumber/src/types.ts @@ -0,0 +1 @@ +export type Caip2ChainId = `${string}:${string}`; diff --git a/packages/blocknumber/src/utils/index.ts b/packages/blocknumber/src/utils/index.ts index 2697322..1c13af1 100644 --- a/packages/blocknumber/src/utils/index.ts +++ b/packages/blocknumber/src/utils/index.ts @@ -1,2 +1,2 @@ -export * from "./chainId.js"; +export * from "./caip/caip2.js"; export * from "./logger.js"; diff --git a/packages/blocknumber/test/services/blockNumberService.spec.ts b/packages/blocknumber/test/services/blockNumberService.spec.ts new file mode 100644 index 0000000..4cc24ee --- /dev/null +++ b/packages/blocknumber/test/services/blockNumberService.spec.ts @@ -0,0 +1,164 @@ +import { createPublicClient, createTestClient, fallback, http } from "viem"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { EmptyRpcUrls, NoProvider, UnsupportedChain } from "../../src/exceptions/index.js"; +import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider.js"; +import { BlockNumberService } from "../../src/services/index.js"; +import { Caip2ChainId } from "../../src/types.js"; +import { chains as caip2Chains } from "../../src/utils/caip/caip2.js"; + +describe("BlockNumberService", () => { + describe("constructor", () => { + const dummyProviders: Record = { + [caip2Chains.mainnet]: { + getEpochBlockNumber: async () => undefined, + providerClass: EvmBlockNumberProvider, + }, + [caip2Chains.polygon]: { + getEpochBlockNumber: async () => undefined, + providerClass: EvmBlockNumberProvider, + }, + }; + + const providersChains = Object.keys(dummyProviders) as Caip2ChainId[]; + const rpcUrls = new Map( + providersChains.map((chain) => [ + chain, + ["http://localhost:8545", "http://localhost:8546"], + ]), + ); + + beforeEach(() => { + spyWithDummyProviders(dummyProviders); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("creates an instance of BlockNumberService", () => { + const service = new BlockNumberService(rpcUrls); + + expect(service).toBeInstanceOf(BlockNumberService); + }); + + it("fails if initialized without any chain", () => { + const emptyRpcUrls = new Map(); + + expect(() => new BlockNumberService(emptyRpcUrls)).toThrow(EmptyRpcUrls); + }); + }); + + describe("buildProvider", () => { + const client = createPublicClient({ transport: fallback([http("http://localhost:8545")]) }); + + it("builds a provider", () => { + const provider = BlockNumberService.buildProvider(caip2Chains.mainnet, client); + + expect(provider).toBeInstanceOf(EvmBlockNumberProvider); + }); + + it("fails if chain is not supported", () => { + const unsupportedChainId = "eip155:80085" as Caip2ChainId; + + expect(() => { + BlockNumberService.buildProvider(unsupportedChainId, client); + }).toThrow(UnsupportedChain); + }); + }); + + describe("getEpochBlockNumbers", () => { + const dummyProviders: Record = { + [caip2Chains.mainnet]: { + getEpochBlockNumber: async () => 1234n, + providerClass: EvmBlockNumberProvider, + }, + [caip2Chains.polygon]: { + getEpochBlockNumber: async () => 5678n, + providerClass: EvmBlockNumberProvider, + }, + } as const; + + const providersChains = Object.keys(dummyProviders) as Caip2ChainId[]; + const rpcUrls = new Map( + providersChains.map((chain) => [ + chain, + ["http://localhost:8545", "http://localhost:8546"], + ]), + ); + + beforeEach(() => { + spyWithDummyProviders(dummyProviders); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("returns the chains' epoch block numbers", async () => { + const service = new BlockNumberService(rpcUrls); + + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const epochChains = [caip2Chains.mainnet, caip2Chains.polygon]; + const blockNumbers = await service.getEpochBlockNumbers(timestamp, epochChains); + + epochChains.forEach(async (chain) => { + const blockNumber = await dummyProviders[chain].getEpochBlockNumber(); + + expect(blockNumbers.get(chain)).toEqual(blockNumber); + }); + }); + + it("fails if some input chain has no provider assigned", async () => { + const service = new BlockNumberService(rpcUrls); + + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const epochChains = [caip2Chains.mainnet, caip2Chains.arbitrum]; + + expect(service.getEpochBlockNumbers(timestamp, epochChains)).rejects.toThrow( + NoProvider, + ); + }); + + it("fails if a provider fails", async () => { + const failingProviders = { + [caip2Chains.mainnet]: { + getEpochBlockNumber: async () => { + throw new Error(); + }, + providerClass: EvmBlockNumberProvider, + }, + }; + + spyWithDummyProviders(failingProviders); + + const rpcUrls: Map = new Map([ + [caip2Chains.mainnet, ["http://localhost:8545"]], + ]); + + const service = new BlockNumberService(rpcUrls); + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + + expect( + service.getEpochBlockNumbers(timestamp, [caip2Chains.mainnet]), + ).rejects.toBeDefined(); + + vi.clearAllMocks(); + }); + }); +}); + +function spyWithDummyProviders(providersResults) { + type buildProviderArgs = Parameters; + const dummyProvider = (chainId: buildProviderArgs[0], client: buildProviderArgs[1]) => { + const provider = new providersResults[chainId].providerClass(client, {}); + + provider.getEpochBlockNumber = vi + .fn() + .mockImplementation(providersResults[chainId].getEpochBlockNumber); + + return provider; + }; + + vi.spyOn(BlockNumberService, "buildProvider").mockImplementation(dummyProvider); +} From cc0a6f0c60ceff34c79064b7e30796d69563c01c Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 26 Jul 2024 17:01:36 -0300 Subject: [PATCH 21/32] chore: fix vitest tests location config --- packages/blocknumber/vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blocknumber/vitest.config.ts b/packages/blocknumber/vitest.config.ts index fec3430..28ac031 100644 --- a/packages/blocknumber/vitest.config.ts +++ b/packages/blocknumber/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ test: { globals: true, // Use Vitest's global API without importing it in each file environment: "node", // Use the Node.js environment - include: ["src/**/*.spec.ts"], // Include test files + include: ["test/**/*.spec.ts"], // Include test files exclude: ["node_modules", "dist"], // Exclude certain directories coverage: { provider: "v8", From 0b44abe5a4ba7ccb04ce30c2dc918c5b5beee6aa Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 30 Jul 2024 11:54:28 -0300 Subject: [PATCH 22/32] feat: build provider based on chain namespace only --- .../src/services/blockNumberService.ts | 17 ++++++++----- packages/blocknumber/src/utils/caip/caip2.ts | 16 ++++++------ .../test/services/blockNumberService.spec.ts | 25 ++++++++----------- .../caip/{caip.spec.ts => caip2.spec.ts} | 15 +++++++++++ 4 files changed, 45 insertions(+), 28 deletions(-) rename packages/blocknumber/test/utils/caip/{caip.spec.ts => caip2.spec.ts} (69%) diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts index 05d05ff..da11eba 100644 --- a/packages/blocknumber/src/services/blockNumberService.ts +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -11,9 +11,15 @@ import { ChainWithoutProvider, EmptyRpcUrls, UnsupportedChain } from "../excepti import { BlockNumberProvider } from "../providers/blockNumberProvider.js"; import { EvmBlockNumberProvider } from "../providers/evmBlockNumberProvider.js"; import { Caip2ChainId } from "../types.js"; +import { Caip2 } from "../utils/index.js"; type RpcUrl = NonNullable[0]>; +const DEFAULT_PROVIDER_CONFIG = { + blocksLookback: 10_000n, + deltaMultiplier: 2n, +}; + export class BlockNumberService { private blockNumberProviders: Map; @@ -65,12 +71,11 @@ export class BlockNumberService { chainId: Caip2ChainId, client: PublicClient>, ) { - switch (chainId) { - case "eip155:1": - return new EvmBlockNumberProvider(client, { - blocksLookback: 10_000n, - deltaMultiplier: 2n, - }); + const chainNamespace = Caip2.getNamespace(chainId); + + switch (chainNamespace) { + case "eip155": + return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG); default: throw new UnsupportedChain(chainId); diff --git a/packages/blocknumber/src/utils/caip/caip2.ts b/packages/blocknumber/src/utils/caip/caip2.ts index f2a5a09..0697488 100644 --- a/packages/blocknumber/src/utils/caip/caip2.ts +++ b/packages/blocknumber/src/utils/caip/caip2.ts @@ -6,14 +6,6 @@ import { Caip2ChainId } from "../../types.js"; const NAMESPACE_FORMAT = /^[-a-z0-9]{3,8}$/; const REFERENCE_FORMAT = /^[-_a-zA-Z0-9]{1,32}$/; -type SupportedChains = "mainnet" | "polygon" | "arbitrum"; - -export const chains = { - mainnet: "eip155:1", - polygon: "eip155:137", - arbitrum: "eip155:42161", -} as Record; - export class Caip2 { /** * Parses a CAIP-2 compliant string. @@ -42,4 +34,12 @@ export class Caip2 { return true; } + + public static getNamespace(chainId: string | Caip2ChainId) { + this.validateChainId(chainId); + + const namespace = chainId.split(":")[0] as string; + + return namespace; + } } diff --git a/packages/blocknumber/test/services/blockNumberService.spec.ts b/packages/blocknumber/test/services/blockNumberService.spec.ts index ac234f2..d1033cf 100644 --- a/packages/blocknumber/test/services/blockNumberService.spec.ts +++ b/packages/blocknumber/test/services/blockNumberService.spec.ts @@ -9,16 +9,15 @@ import { import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider.js"; import { BlockNumberService } from "../../src/services/index.js"; import { Caip2ChainId } from "../../src/types.js"; -import { chains as caip2Chains } from "../../src/utils/caip/caip2.js"; describe("BlockNumberService", () => { describe("constructor", () => { const dummyProviders: Record = { - [caip2Chains.mainnet]: { + "eip155:1": { getEpochBlockNumber: async () => undefined, providerClass: EvmBlockNumberProvider, }, - [caip2Chains.polygon]: { + "eip155:137": { getEpochBlockNumber: async () => undefined, providerClass: EvmBlockNumberProvider, }, @@ -57,13 +56,13 @@ describe("BlockNumberService", () => { const client = createPublicClient({ transport: fallback([http("http://localhost:8545")]) }); it("builds a provider", () => { - const provider = BlockNumberService.buildProvider(caip2Chains.mainnet, client); + const provider = BlockNumberService.buildProvider("eip155:1", client); expect(provider).toBeInstanceOf(EvmBlockNumberProvider); }); it("fails if chain is not supported", () => { - const unsupportedChainId = "eip155:80085" as Caip2ChainId; + const unsupportedChainId = "solana:80085" as Caip2ChainId; expect(() => { BlockNumberService.buildProvider(unsupportedChainId, client); @@ -73,11 +72,11 @@ describe("BlockNumberService", () => { describe("getEpochBlockNumbers", () => { const dummyProviders: Record = { - [caip2Chains.mainnet]: { + "eip155:1": { getEpochBlockNumber: async () => 1234n, providerClass: EvmBlockNumberProvider, }, - [caip2Chains.polygon]: { + "eip155:137": { getEpochBlockNumber: async () => 5678n, providerClass: EvmBlockNumberProvider, }, @@ -103,7 +102,7 @@ describe("BlockNumberService", () => { const service = new BlockNumberService(rpcUrls); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); - const epochChains = [caip2Chains.mainnet, caip2Chains.polygon]; + const epochChains = ["eip155:1", "eip155:137"] as Caip2ChainId[]; const blockNumbers = await service.getEpochBlockNumbers(timestamp, epochChains); epochChains.forEach(async (chain) => { @@ -117,7 +116,7 @@ describe("BlockNumberService", () => { const service = new BlockNumberService(rpcUrls); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); - const epochChains = [caip2Chains.mainnet, caip2Chains.arbitrum]; + const epochChains = ["eip155:1", "eip155:42161"] as Caip2ChainId[]; expect(service.getEpochBlockNumbers(timestamp, epochChains)).rejects.toThrow( ChainWithoutProvider, @@ -126,7 +125,7 @@ describe("BlockNumberService", () => { it("fails if a provider fails", async () => { const failingProviders = { - [caip2Chains.mainnet]: { + "eip155:1": { getEpochBlockNumber: async () => { throw new Error(); }, @@ -137,15 +136,13 @@ describe("BlockNumberService", () => { spyWithDummyProviders(failingProviders); const rpcUrls: Map = new Map([ - [caip2Chains.mainnet, ["http://localhost:8545"]], + ["eip155:1", ["http://localhost:8545"]], ]); const service = new BlockNumberService(rpcUrls); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); - expect( - service.getEpochBlockNumbers(timestamp, [caip2Chains.mainnet]), - ).rejects.toBeDefined(); + expect(service.getEpochBlockNumbers(timestamp, ["eip155:1"])).rejects.toBeDefined(); vi.clearAllMocks(); }); diff --git a/packages/blocknumber/test/utils/caip/caip.spec.ts b/packages/blocknumber/test/utils/caip/caip2.spec.ts similarity index 69% rename from packages/blocknumber/test/utils/caip/caip.spec.ts rename to packages/blocknumber/test/utils/caip/caip2.spec.ts index d444337..4cb26f0 100644 --- a/packages/blocknumber/test/utils/caip/caip.spec.ts +++ b/packages/blocknumber/test/utils/caip/caip2.spec.ts @@ -29,4 +29,19 @@ describe("Caip2", () => { expect(() => Caip2.validateChainId(chainId)).toThrowError(InvalidChainId); }); }); + + describe("getNamespace", () => { + it("returns the namespace of a caip-2 compliant chain id", () => { + const chainId = "eip155:137"; + const result = Caip2.getNamespace(chainId); + + expect(result).toEqual("eip155"); + }); + + it("throws an error if the chain is invalid", () => { + const chainId = "foo:!nval!d"; + + expect(() => Caip2.getNamespace(chainId)).toThrowError(); + }); + }); }); From 557a0730502853dd0497201ce251ff66b4f7efcd Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 30 Jul 2024 20:11:28 -0300 Subject: [PATCH 23/32] chore: remove old logger class --- packages/blocknumber/src/utils/logger.ts | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 packages/blocknumber/src/utils/logger.ts diff --git a/packages/blocknumber/src/utils/logger.ts b/packages/blocknumber/src/utils/logger.ts deleted file mode 100644 index c18686d..0000000 --- a/packages/blocknumber/src/utils/logger.ts +++ /dev/null @@ -1,15 +0,0 @@ -import winston from "winston"; - -const logger = winston.createLogger({ - level: "info", - format: winston.format.json(), - defaultMeta: { service: "blocknumber" }, - transports: [ - new winston.transports.Console({ - format: winston.format.simple(), - silent: process.env.NODE_ENV == "test", - }), - ], -}); - -export default logger; From 163669b3e6293e746b37990e3ac7de7d2f38aa8e Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 30 Jul 2024 21:14:53 -0300 Subject: [PATCH 24/32] fix: remove references from old logger --- .../src/providers/evmBlockNumberProvider.ts | 7 +++---- packages/blocknumber/src/utils/index.ts | 1 - .../test/providers/evmBlockNumberProvider.spec.ts | 15 ++++++++------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts index ae7a652..b986625 100644 --- a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -9,7 +9,6 @@ import { UnsupportedBlockNumber, UnsupportedBlockTimestamps, } from "../exceptions/index.js"; -import logger from "../utils/logger.js"; import { BlockNumberProvider } from "./blockNumberProvider.js"; const BINARY_SEARCH_BLOCKS_LOOKBACK = 10_000n; @@ -201,7 +200,7 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { if (low > high) throw new UnexpectedSearchRange(low, high); - logger.debug(`Starting block binary search for timestamp ${timestamp}...`); + this.logger.debug(`Starting block binary search for timestamp ${timestamp}...`); while (low <= high) { currentBlockNumber = (high + low) / 2n; @@ -209,7 +208,7 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { const currentBlock = await this.client.getBlock({ blockNumber: currentBlockNumber }); const nextBlock = await this.client.getBlock({ blockNumber: currentBlockNumber + 1n }); - logger.debug( + this.logger.debug( `Analyzing block number #${currentBlock.number} with timestamp ${currentBlock.timestamp}`, ); @@ -223,7 +222,7 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { currentBlock.timestamp <= timestamp && nextBlock.timestamp > timestamp; if (blockContainsTimestamp) { - logger.debug(`Block #${currentBlock.number} contains timestamp.`); + this.logger.debug(`Block #${currentBlock.number} contains timestamp.`); return currentBlock.number; } else if (currentBlock.timestamp <= timestamp) { diff --git a/packages/blocknumber/src/utils/index.ts b/packages/blocknumber/src/utils/index.ts index 1c13af1..112754c 100644 --- a/packages/blocknumber/src/utils/index.ts +++ b/packages/blocknumber/src/utils/index.ts @@ -1,2 +1 @@ export * from "./caip/caip2.js"; -export * from "./logger.js"; diff --git a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts index e2ed814..41f01c3 100644 --- a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts +++ b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts @@ -1,3 +1,4 @@ +import { Logger } from "@ebo-agent/shared"; import { Block, createPublicClient, GetBlockParameters, http } from "viem"; import { mainnet } from "viem/chains"; import { describe, expect, it, vi } from "vitest"; @@ -21,7 +22,7 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 11, 0, 0, 0, 0); const rpcProvider = mockRpcProvider(blockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig, logger); const day5 = Date.UTC(2024, 1, 5, 2, 0, 0, 0); const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5); @@ -35,7 +36,7 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig, logger); const exactDay5 = Date.UTC(2024, 1, 1, 0, 0, 5, 0); const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5); @@ -49,7 +50,7 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig, logger); const futureTimestamp = Date.UTC(2025, 1, 1, 0, 0, 0, 0); @@ -64,7 +65,7 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig, logger); const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); @@ -84,7 +85,7 @@ describe("EvmBlockNumberProvider", () => { { number: 4n, timestamp: afterTimestamp }, ]); - evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig, logger); expect(evmProvider.getEpochBlockNumber(Number(timestamp))).rejects.toBeInstanceOf( UnsupportedBlockTimestamps, @@ -97,7 +98,7 @@ describe("EvmBlockNumberProvider", () => { { number: null, timestamp: BigInt(timestamp) }, ]); - evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig, logger); expect(evmProvider.getEpochBlockNumber(Number(timestamp))).rejects.toBeInstanceOf( UnsupportedBlockNumber, @@ -109,7 +110,7 @@ describe("EvmBlockNumberProvider", () => { client.getBlock = vi.fn().mockRejectedValue(null); - evmProvider = new EvmBlockNumberProvider(client, searchConfig); + evmProvider = new EvmBlockNumberProvider(client, searchConfig, logger); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); expect(evmProvider.getEpochBlockNumber(timestamp)).rejects.toBeDefined(); From 776ba42cd8aa591e6a81a5ca90992ac562c28a91 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 30 Jul 2024 21:15:27 -0300 Subject: [PATCH 25/32] refactor: use shared chains --- .../src/services/blockNumberService.ts | 20 +++++++++++++++++-- .../providers/evmBlockNumberProvider.spec.ts | 1 + packages/shared/src/constants.ts | 10 ++++++++++ turbo.json | 4 +++- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts index da11eba..8088cfe 100644 --- a/packages/blocknumber/src/services/blockNumberService.ts +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -1,3 +1,4 @@ +import { Logger, supportedChains } from "@ebo-agent/shared"; import { createPublicClient, fallback, @@ -50,9 +51,12 @@ export class BlockNumberService { private buildBlockNumberProviders(chainRpcUrls: Map) { if (chainRpcUrls.size == 0) throw new EmptyRpcUrls(); + const supportedChainIds = this.getSupportedChainIds(supportedChains); const providers = new Map(); for (const [chainId, urls] of chainRpcUrls) { + if (!supportedChainIds.includes(chainId)) throw new UnsupportedChain(chainId); + const client = createPublicClient({ transport: fallback(urls.map((url) => http(url))), }); @@ -67,6 +71,14 @@ export class BlockNumberService { return providers; } + private getSupportedChainIds(chainsConfig: typeof supportedChains) { + const namespacesChains = Object.values(chainsConfig); + + return namespacesChains.reduce((acc, namespaceChains) => { + return [...acc, ...Object.values(namespaceChains.chains)]; + }, [] as string[]); + } + public static buildProvider( chainId: Caip2ChainId, client: PublicClient>, @@ -74,8 +86,12 @@ export class BlockNumberService { const chainNamespace = Caip2.getNamespace(chainId); switch (chainNamespace) { - case "eip155": - return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG); + case supportedChains.evm.namespace: + return new EvmBlockNumberProvider( + client, + DEFAULT_PROVIDER_CONFIG, + Logger.getInstance(), // Should we drop this arg? + ); default: throw new UnsupportedChain(chainId); diff --git a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts index 41f01c3..5bf18fd 100644 --- a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts +++ b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts @@ -14,6 +14,7 @@ import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvid describe("EvmBlockNumberProvider", () => { describe("getEpochBlockNumber", () => { const searchConfig = { blocksLookback: 2n, deltaMultiplier: 2n }; + const logger = Logger.getInstance(); let evmProvider: EvmBlockNumberProvider; it("returns the first of two consecutive blocks when their timestamp contains the searched timestamp", async () => { diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index e69de29..2c7074f 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -0,0 +1,10 @@ +export const supportedChains = { + evm: { + namespace: "eip155", + chains: { + ethereum: "eip155:1", + polygon: "eip155:137", + arbitrum: "eip155:42161", + }, + }, +} as const; diff --git a/turbo.json b/turbo.json index 541985e..5148346 100644 --- a/turbo.json +++ b/turbo.json @@ -7,7 +7,9 @@ "format:fix": {}, "test": {}, "coverage": {}, - "build": {} + "build": { + "dependsOn": ["^build"] + } }, "globalDependencies": [".eslintrc", ".prettierrc"] } From ea13674ebc9a9fed07c1adbfb061ecdce1e6c1af Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 30 Jul 2024 21:28:11 -0300 Subject: [PATCH 26/32] chore: fix turbo config --- turbo.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 5148346..1e7b730 100644 --- a/turbo.json +++ b/turbo.json @@ -5,7 +5,9 @@ "lint:fix": {}, "format": {}, "format:fix": {}, - "test": {}, + "test": { + "dependsOn": ["build"] + }, "coverage": {}, "build": { "dependsOn": ["^build"] From 0c01a4bcf0040f463b8350cc8ba904e17f0ebc9f Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 31 Jul 2024 14:28:51 -0300 Subject: [PATCH 27/32] refactor: use logger with dependency injection --- .../src/services/blockNumberService.ts | 16 ++++++++-------- .../test/services/blockNumberService.spec.ts | 17 ++++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts index 8088cfe..2b19611 100644 --- a/packages/blocknumber/src/services/blockNumberService.ts +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -1,4 +1,4 @@ -import { Logger, supportedChains } from "@ebo-agent/shared"; +import { ILogger, supportedChains } from "@ebo-agent/shared"; import { createPublicClient, fallback, @@ -24,7 +24,10 @@ const DEFAULT_PROVIDER_CONFIG = { export class BlockNumberService { private blockNumberProviders: Map; - constructor(chainRpcUrls: Map) { + constructor( + chainRpcUrls: Map, + private readonly logger: ILogger, + ) { this.blockNumberProviders = this.buildBlockNumberProviders(chainRpcUrls); } @@ -61,7 +64,7 @@ export class BlockNumberService { transport: fallback(urls.map((url) => http(url))), }); - const provider = BlockNumberService.buildProvider(chainId, client); + const provider = BlockNumberService.buildProvider(chainId, client, this.logger); if (!provider) throw new ChainWithoutProvider(chainId); @@ -82,16 +85,13 @@ export class BlockNumberService { public static buildProvider( chainId: Caip2ChainId, client: PublicClient>, + logger: ILogger, ) { const chainNamespace = Caip2.getNamespace(chainId); switch (chainNamespace) { case supportedChains.evm.namespace: - return new EvmBlockNumberProvider( - client, - DEFAULT_PROVIDER_CONFIG, - Logger.getInstance(), // Should we drop this arg? - ); + return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG, logger); default: throw new UnsupportedChain(chainId); diff --git a/packages/blocknumber/test/services/blockNumberService.spec.ts b/packages/blocknumber/test/services/blockNumberService.spec.ts index d1033cf..c50fed3 100644 --- a/packages/blocknumber/test/services/blockNumberService.spec.ts +++ b/packages/blocknumber/test/services/blockNumberService.spec.ts @@ -1,3 +1,4 @@ +import { Logger } from "@ebo-agent/shared"; import { createPublicClient, fallback, http } from "viem"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -11,6 +12,8 @@ import { BlockNumberService } from "../../src/services/index.js"; import { Caip2ChainId } from "../../src/types.js"; describe("BlockNumberService", () => { + const logger = Logger.getInstance(); + describe("constructor", () => { const dummyProviders: Record = { "eip155:1": { @@ -40,7 +43,7 @@ describe("BlockNumberService", () => { }); it("creates an instance of BlockNumberService", () => { - const service = new BlockNumberService(rpcUrls); + const service = new BlockNumberService(rpcUrls, logger); expect(service).toBeInstanceOf(BlockNumberService); }); @@ -48,7 +51,7 @@ describe("BlockNumberService", () => { it("fails if initialized without any chain", () => { const emptyRpcUrls = new Map(); - expect(() => new BlockNumberService(emptyRpcUrls)).toThrow(EmptyRpcUrls); + expect(() => new BlockNumberService(emptyRpcUrls, logger)).toThrow(EmptyRpcUrls); }); }); @@ -56,7 +59,7 @@ describe("BlockNumberService", () => { const client = createPublicClient({ transport: fallback([http("http://localhost:8545")]) }); it("builds a provider", () => { - const provider = BlockNumberService.buildProvider("eip155:1", client); + const provider = BlockNumberService.buildProvider("eip155:1", client, logger); expect(provider).toBeInstanceOf(EvmBlockNumberProvider); }); @@ -65,7 +68,7 @@ describe("BlockNumberService", () => { const unsupportedChainId = "solana:80085" as Caip2ChainId; expect(() => { - BlockNumberService.buildProvider(unsupportedChainId, client); + BlockNumberService.buildProvider(unsupportedChainId, client, logger); }).toThrow(UnsupportedChain); }); }); @@ -99,7 +102,7 @@ describe("BlockNumberService", () => { }); it("returns the chains' epoch block numbers", async () => { - const service = new BlockNumberService(rpcUrls); + const service = new BlockNumberService(rpcUrls, logger); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); const epochChains = ["eip155:1", "eip155:137"] as Caip2ChainId[]; @@ -113,7 +116,7 @@ describe("BlockNumberService", () => { }); it("fails if some input chain has no provider assigned", async () => { - const service = new BlockNumberService(rpcUrls); + const service = new BlockNumberService(rpcUrls, logger); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); const epochChains = ["eip155:1", "eip155:42161"] as Caip2ChainId[]; @@ -139,7 +142,7 @@ describe("BlockNumberService", () => { ["eip155:1", ["http://localhost:8545"]], ]); - const service = new BlockNumberService(rpcUrls); + const service = new BlockNumberService(rpcUrls, logger); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); expect(service.getEpochBlockNumbers(timestamp, ["eip155:1"])).rejects.toBeDefined(); From 472dab20957cd712c0bfe1fe88f1cd895891dc45 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 31 Jul 2024 15:27:34 -0300 Subject: [PATCH 28/32] refactor: rename Caip2 to Caip2Utils --- .../src/services/blockNumberService.ts | 4 ++-- .../src/utils/caip/{caip2.ts => caip2Utils.ts} | 2 +- packages/blocknumber/src/utils/caip/index.ts | 2 +- packages/blocknumber/src/utils/index.ts | 2 +- .../caip/{caip2.spec.ts => caip2Utils.spec.ts} | 16 ++++++++-------- 5 files changed, 13 insertions(+), 13 deletions(-) rename packages/blocknumber/src/utils/caip/{caip2.ts => caip2Utils.ts} (98%) rename packages/blocknumber/test/utils/caip/{caip2.spec.ts => caip2Utils.spec.ts} (63%) diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts index 2b19611..4869c42 100644 --- a/packages/blocknumber/src/services/blockNumberService.ts +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -12,7 +12,7 @@ import { ChainWithoutProvider, EmptyRpcUrls, UnsupportedChain } from "../excepti import { BlockNumberProvider } from "../providers/blockNumberProvider.js"; import { EvmBlockNumberProvider } from "../providers/evmBlockNumberProvider.js"; import { Caip2ChainId } from "../types.js"; -import { Caip2 } from "../utils/index.js"; +import { Caip2Utils } from "../utils/index.js"; type RpcUrl = NonNullable[0]>; @@ -87,7 +87,7 @@ export class BlockNumberService { client: PublicClient>, logger: ILogger, ) { - const chainNamespace = Caip2.getNamespace(chainId); + const chainNamespace = Caip2Utils.getNamespace(chainId); switch (chainNamespace) { case supportedChains.evm.namespace: diff --git a/packages/blocknumber/src/utils/caip/caip2.ts b/packages/blocknumber/src/utils/caip/caip2Utils.ts similarity index 98% rename from packages/blocknumber/src/utils/caip/caip2.ts rename to packages/blocknumber/src/utils/caip/caip2Utils.ts index 0697488..3a8b1fb 100644 --- a/packages/blocknumber/src/utils/caip/caip2.ts +++ b/packages/blocknumber/src/utils/caip/caip2Utils.ts @@ -6,7 +6,7 @@ import { Caip2ChainId } from "../../types.js"; const NAMESPACE_FORMAT = /^[-a-z0-9]{3,8}$/; const REFERENCE_FORMAT = /^[-_a-zA-Z0-9]{1,32}$/; -export class Caip2 { +export class Caip2Utils { /** * Parses a CAIP-2 compliant string. * diff --git a/packages/blocknumber/src/utils/caip/index.ts b/packages/blocknumber/src/utils/caip/index.ts index a8a261f..6879cb2 100644 --- a/packages/blocknumber/src/utils/caip/index.ts +++ b/packages/blocknumber/src/utils/caip/index.ts @@ -1 +1 @@ -export * from "./caip2.js"; +export * from "./caip2Utils.js"; diff --git a/packages/blocknumber/src/utils/index.ts b/packages/blocknumber/src/utils/index.ts index 112754c..6c40c0c 100644 --- a/packages/blocknumber/src/utils/index.ts +++ b/packages/blocknumber/src/utils/index.ts @@ -1 +1 @@ -export * from "./caip/caip2.js"; +export * from "./caip/caip2Utils.js"; diff --git a/packages/blocknumber/test/utils/caip/caip2.spec.ts b/packages/blocknumber/test/utils/caip/caip2Utils.spec.ts similarity index 63% rename from packages/blocknumber/test/utils/caip/caip2.spec.ts rename to packages/blocknumber/test/utils/caip/caip2Utils.spec.ts index 4cb26f0..c1260aa 100644 --- a/packages/blocknumber/test/utils/caip/caip2.spec.ts +++ b/packages/blocknumber/test/utils/caip/caip2Utils.spec.ts @@ -1,12 +1,12 @@ import { describe, expect, it } from "vitest"; import { InvalidChainId } from "../../../src/exceptions/invalidChain.js"; -import { Caip2 } from "../../../src/utils/caip/index.js"; +import { Caip2Utils } from "../../../src/utils/caip/index.js"; -describe("Caip2", () => { +describe("Caip2Utils", () => { describe("validateChainId", () => { it("validates a CAIP-2 compliant chain id", () => { - const isValid = Caip2.validateChainId("eip155:1"); + const isValid = Caip2Utils.validateChainId("eip155:1"); expect(isValid).toBe(true); }); @@ -14,26 +14,26 @@ describe("Caip2", () => { it("fails when input chain id is not caip-2 compliant", () => { const chainId = "foobar"; - expect(() => Caip2.validateChainId(chainId)).toThrowError(InvalidChainId); + expect(() => Caip2Utils.validateChainId(chainId)).toThrowError(InvalidChainId); }); it("fails when input namespace is not caip-2 compliant", () => { const chainId = "f:1"; - expect(() => Caip2.validateChainId(chainId)).toThrowError(InvalidChainId); + expect(() => Caip2Utils.validateChainId(chainId)).toThrowError(InvalidChainId); }); it("fails when input reference is not caip-2 compliant", () => { const chainId = "foo:!nval!d"; - expect(() => Caip2.validateChainId(chainId)).toThrowError(InvalidChainId); + expect(() => Caip2Utils.validateChainId(chainId)).toThrowError(InvalidChainId); }); }); describe("getNamespace", () => { it("returns the namespace of a caip-2 compliant chain id", () => { const chainId = "eip155:137"; - const result = Caip2.getNamespace(chainId); + const result = Caip2Utils.getNamespace(chainId); expect(result).toEqual("eip155"); }); @@ -41,7 +41,7 @@ describe("Caip2", () => { it("throws an error if the chain is invalid", () => { const chainId = "foo:!nval!d"; - expect(() => Caip2.getNamespace(chainId)).toThrowError(); + expect(() => Caip2Utils.getNamespace(chainId)).toThrowError(); }); }); }); From 0954b5788508f18f3d71f0da972fa0e97c68ab25 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 31 Jul 2024 15:45:48 -0300 Subject: [PATCH 29/32] refactor: created BlockNumberProviderFactory --- .../providers/blockNumberProviderFactory.ts | 30 ++++++++++++++ .../src/services/blockNumberService.ts | 39 +++---------------- .../blockNumberProviderFactory.spec.ts | 30 ++++++++++++++ .../test/services/blockNumberService.spec.ts | 28 ++----------- 4 files changed, 68 insertions(+), 59 deletions(-) create mode 100644 packages/blocknumber/src/providers/blockNumberProviderFactory.ts create mode 100644 packages/blocknumber/test/providers/blockNumberProviderFactory.spec.ts diff --git a/packages/blocknumber/src/providers/blockNumberProviderFactory.ts b/packages/blocknumber/src/providers/blockNumberProviderFactory.ts new file mode 100644 index 0000000..8ff1b70 --- /dev/null +++ b/packages/blocknumber/src/providers/blockNumberProviderFactory.ts @@ -0,0 +1,30 @@ +import { ILogger, supportedChains } from "@ebo-agent/shared"; +import { FallbackTransport, HttpTransport, PublicClient } from "viem"; + +import { UnsupportedChain } from "../exceptions/unsupportedChain.js"; +import { Caip2ChainId } from "../types.js"; +import { Caip2Utils } from "../utils/index.js"; +import { EvmBlockNumberProvider } from "./evmBlockNumberProvider.js"; + +const DEFAULT_PROVIDER_CONFIG = { + blocksLookback: 10_000n, + deltaMultiplier: 2n, +}; + +export class BlockNumberProviderFactory { + public static buildProvider( + chainId: Caip2ChainId, + client: PublicClient>, + logger: ILogger, + ) { + const chainNamespace = Caip2Utils.getNamespace(chainId); + + switch (chainNamespace) { + case supportedChains.evm.namespace: + return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG, logger); + + default: + throw new UnsupportedChain(chainId); + } + } +} diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts index 4869c42..80e65d7 100644 --- a/packages/blocknumber/src/services/blockNumberService.ts +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -1,26 +1,13 @@ import { ILogger, supportedChains } from "@ebo-agent/shared"; -import { - createPublicClient, - fallback, - FallbackTransport, - http, - HttpTransport, - PublicClient, -} from "viem"; +import { createPublicClient, fallback, http } from "viem"; import { ChainWithoutProvider, EmptyRpcUrls, UnsupportedChain } from "../exceptions/index.js"; import { BlockNumberProvider } from "../providers/blockNumberProvider.js"; -import { EvmBlockNumberProvider } from "../providers/evmBlockNumberProvider.js"; +import { BlockNumberProviderFactory } from "../providers/blockNumberProviderFactory.js"; import { Caip2ChainId } from "../types.js"; -import { Caip2Utils } from "../utils/index.js"; type RpcUrl = NonNullable[0]>; -const DEFAULT_PROVIDER_CONFIG = { - blocksLookback: 10_000n, - deltaMultiplier: 2n, -}; - export class BlockNumberService { private blockNumberProviders: Map; @@ -44,11 +31,11 @@ export class BlockNumberService { }), ); - const e = epochBlockNumbers.filter( + const epochBlockNumbersMap = epochBlockNumbers.filter( (entry): entry is [Caip2ChainId, bigint] => entry !== null, ); - return new Map(e); + return new Map(epochBlockNumbersMap); } private buildBlockNumberProviders(chainRpcUrls: Map) { @@ -64,7 +51,7 @@ export class BlockNumberService { transport: fallback(urls.map((url) => http(url))), }); - const provider = BlockNumberService.buildProvider(chainId, client, this.logger); + const provider = BlockNumberProviderFactory.buildProvider(chainId, client, this.logger); if (!provider) throw new ChainWithoutProvider(chainId); @@ -81,20 +68,4 @@ export class BlockNumberService { return [...acc, ...Object.values(namespaceChains.chains)]; }, [] as string[]); } - - public static buildProvider( - chainId: Caip2ChainId, - client: PublicClient>, - logger: ILogger, - ) { - const chainNamespace = Caip2Utils.getNamespace(chainId); - - switch (chainNamespace) { - case supportedChains.evm.namespace: - return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG, logger); - - default: - throw new UnsupportedChain(chainId); - } - } } diff --git a/packages/blocknumber/test/providers/blockNumberProviderFactory.spec.ts b/packages/blocknumber/test/providers/blockNumberProviderFactory.spec.ts new file mode 100644 index 0000000..9c192ec --- /dev/null +++ b/packages/blocknumber/test/providers/blockNumberProviderFactory.spec.ts @@ -0,0 +1,30 @@ +import { Logger } from "@ebo-agent/shared"; +import { createPublicClient, fallback, http } from "viem"; +import { describe, expect, it } from "vitest"; + +import { UnsupportedChain } from "../../src/exceptions"; +import { BlockNumberProviderFactory } from "../../src/providers/blockNumberProviderFactory"; +import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider"; +import { Caip2ChainId } from "../../src/types"; + +describe("BlockNumberProviderFactory", () => { + const logger = Logger.getInstance(); + + describe("buildProvider", () => { + const client = createPublicClient({ transport: fallback([http("http://localhost:8545")]) }); + + it("builds a provider", () => { + const provider = BlockNumberProviderFactory.buildProvider("eip155:1", client, logger); + + expect(provider).toBeInstanceOf(EvmBlockNumberProvider); + }); + + it("fails if chain is not supported", () => { + const unsupportedChainId = "solana:80085" as Caip2ChainId; + + expect(() => { + BlockNumberProviderFactory.buildProvider(unsupportedChainId, client, logger); + }).toThrow(UnsupportedChain); + }); + }); +}); diff --git a/packages/blocknumber/test/services/blockNumberService.spec.ts b/packages/blocknumber/test/services/blockNumberService.spec.ts index c50fed3..a036c45 100644 --- a/packages/blocknumber/test/services/blockNumberService.spec.ts +++ b/packages/blocknumber/test/services/blockNumberService.spec.ts @@ -1,12 +1,8 @@ import { Logger } from "@ebo-agent/shared"; -import { createPublicClient, fallback, http } from "viem"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - ChainWithoutProvider, - EmptyRpcUrls, - UnsupportedChain, -} from "../../src/exceptions/index.js"; +import { ChainWithoutProvider, EmptyRpcUrls } from "../../src/exceptions/index.js"; +import { BlockNumberProviderFactory } from "../../src/providers/blockNumberProviderFactory.js"; import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider.js"; import { BlockNumberService } from "../../src/services/index.js"; import { Caip2ChainId } from "../../src/types.js"; @@ -55,24 +51,6 @@ describe("BlockNumberService", () => { }); }); - describe("buildProvider", () => { - const client = createPublicClient({ transport: fallback([http("http://localhost:8545")]) }); - - it("builds a provider", () => { - const provider = BlockNumberService.buildProvider("eip155:1", client, logger); - - expect(provider).toBeInstanceOf(EvmBlockNumberProvider); - }); - - it("fails if chain is not supported", () => { - const unsupportedChainId = "solana:80085" as Caip2ChainId; - - expect(() => { - BlockNumberService.buildProvider(unsupportedChainId, client, logger); - }).toThrow(UnsupportedChain); - }); - }); - describe("getEpochBlockNumbers", () => { const dummyProviders: Record = { "eip155:1": { @@ -164,5 +142,5 @@ function spyWithDummyProviders(providersResults) { return provider; }; - vi.spyOn(BlockNumberService, "buildProvider").mockImplementation(dummyProvider); + vi.spyOn(BlockNumberProviderFactory, "buildProvider").mockImplementation(dummyProvider); } From 7f0fa3a560e1e84f96a7e411f1731240a2d10dbc Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 31 Jul 2024 16:01:16 -0300 Subject: [PATCH 30/32] feat: add single-chain getEpochBlockNumber --- .../src/services/blockNumberService.ts | 27 ++++--- .../test/services/blockNumberService.spec.ts | 74 +++++++++++++++++++ 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts index 80e65d7..3ee1fe3 100644 --- a/packages/blocknumber/src/services/blockNumberService.ts +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -18,24 +18,27 @@ export class BlockNumberService { this.blockNumberProviders = this.buildBlockNumberProviders(chainRpcUrls); } - public async getEpochBlockNumbers(timestamp: number, chains: Caip2ChainId[]) { - const epochBlockNumbers = await Promise.all( - chains.map(async (chainId) => { - const provider = this.blockNumberProviders.get(chainId); + public async getEpochBlockNumber(timestamp: number, chainId: Caip2ChainId): Promise { + const provider = this.blockNumberProviders.get(chainId); - if (!provider) throw new ChainWithoutProvider(chainId); + if (!provider) throw new ChainWithoutProvider(chainId); - const blockNumber = await provider.getEpochBlockNumber(timestamp); + const blockNumber = await provider.getEpochBlockNumber(timestamp); - return [chainId, blockNumber] as [Caip2ChainId, bigint]; - }), - ); + return blockNumber; + } - const epochBlockNumbersMap = epochBlockNumbers.filter( - (entry): entry is [Caip2ChainId, bigint] => entry !== null, + public async getEpochBlockNumbers(timestamp: number, chains: Caip2ChainId[]) { + const epochBlockNumbers = await Promise.all( + chains.map(async (chain) => ({ + chainId: chain, + blockNumber: await this.getEpochBlockNumber(timestamp, chain), + })), ); - return new Map(epochBlockNumbersMap); + return epochBlockNumbers.reduce((epochBlockNumbersMap, epoch) => { + return epochBlockNumbersMap.set(epoch.chainId, epoch.blockNumber); + }, new Map()); } private buildBlockNumberProviders(chainRpcUrls: Map) { diff --git a/packages/blocknumber/test/services/blockNumberService.spec.ts b/packages/blocknumber/test/services/blockNumberService.spec.ts index a036c45..1951c19 100644 --- a/packages/blocknumber/test/services/blockNumberService.spec.ts +++ b/packages/blocknumber/test/services/blockNumberService.spec.ts @@ -51,6 +51,80 @@ describe("BlockNumberService", () => { }); }); + describe("getEpochBlockNumber", () => { + const dummyProviders: Record = { + "eip155:1": { + getEpochBlockNumber: async () => 1234n, + providerClass: EvmBlockNumberProvider, + }, + "eip155:137": { + getEpochBlockNumber: async () => 5678n, + providerClass: EvmBlockNumberProvider, + }, + } as const; + + const providersChains = Object.keys(dummyProviders) as Caip2ChainId[]; + const rpcUrls = new Map( + providersChains.map((chain) => [ + chain, + ["http://localhost:8545", "http://localhost:8546"], + ]), + ); + + beforeEach(() => { + spyWithDummyProviders(dummyProviders); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("returns the chain epoch block number", async () => { + const service = new BlockNumberService(rpcUrls, logger); + + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const epochChainId = "eip155:1" as Caip2ChainId; + const blockNumber = await service.getEpochBlockNumber(timestamp, epochChainId); + + expect(blockNumber).toEqual(1234n); + }); + + it("fails if the chain has no provider assigned", async () => { + const service = new BlockNumberService(rpcUrls, logger); + + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const epochChainId = "eip155:42161" as Caip2ChainId; + + expect(service.getEpochBlockNumber(timestamp, epochChainId)).rejects.toThrow( + ChainWithoutProvider, + ); + }); + + it("fails if the provider fails", () => { + const failingProviders = { + "eip155:1": { + getEpochBlockNumber: async () => { + throw new Error(); + }, + providerClass: EvmBlockNumberProvider, + }, + }; + + spyWithDummyProviders(failingProviders); + + const rpcUrls: Map = new Map([ + ["eip155:1", ["http://localhost:8545"]], + ]); + + const service = new BlockNumberService(rpcUrls, logger); + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + + expect(service.getEpochBlockNumber(timestamp, "eip155:1")).rejects.toBeDefined(); + + vi.clearAllMocks(); + }); + }); + describe("getEpochBlockNumbers", () => { const dummyProviders: Record = { "eip155:1": { From e97d3672cc823de9895868074e9507ed10e80a02 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 31 Jul 2024 16:14:34 -0300 Subject: [PATCH 31/32] refactor: reorganize chains config structure and values --- .../providers/blockNumberProviderFactory.ts | 4 ++-- .../src/services/blockNumberService.ts | 13 ++++------- packages/shared/src/constants.ts | 22 ++++++++++++++----- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/blocknumber/src/providers/blockNumberProviderFactory.ts b/packages/blocknumber/src/providers/blockNumberProviderFactory.ts index 8ff1b70..3db5594 100644 --- a/packages/blocknumber/src/providers/blockNumberProviderFactory.ts +++ b/packages/blocknumber/src/providers/blockNumberProviderFactory.ts @@ -1,4 +1,4 @@ -import { ILogger, supportedChains } from "@ebo-agent/shared"; +import { EBO_SUPPORTED_CHAINS_CONFIG, ILogger } from "@ebo-agent/shared"; import { FallbackTransport, HttpTransport, PublicClient } from "viem"; import { UnsupportedChain } from "../exceptions/unsupportedChain.js"; @@ -20,7 +20,7 @@ export class BlockNumberProviderFactory { const chainNamespace = Caip2Utils.getNamespace(chainId); switch (chainNamespace) { - case supportedChains.evm.namespace: + case EBO_SUPPORTED_CHAINS_CONFIG.evm.namespace: return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG, logger); default: diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts index 3ee1fe3..94b084b 100644 --- a/packages/blocknumber/src/services/blockNumberService.ts +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -1,4 +1,4 @@ -import { ILogger, supportedChains } from "@ebo-agent/shared"; +import { EBO_SUPPORTED_CHAIN_IDS, ILogger } from "@ebo-agent/shared"; import { createPublicClient, fallback, http } from "viem"; import { ChainWithoutProvider, EmptyRpcUrls, UnsupportedChain } from "../exceptions/index.js"; @@ -44,11 +44,10 @@ export class BlockNumberService { private buildBlockNumberProviders(chainRpcUrls: Map) { if (chainRpcUrls.size == 0) throw new EmptyRpcUrls(); - const supportedChainIds = this.getSupportedChainIds(supportedChains); const providers = new Map(); for (const [chainId, urls] of chainRpcUrls) { - if (!supportedChainIds.includes(chainId)) throw new UnsupportedChain(chainId); + if (!this.isChainSupported(chainId)) throw new UnsupportedChain(chainId); const client = createPublicClient({ transport: fallback(urls.map((url) => http(url))), @@ -64,11 +63,7 @@ export class BlockNumberService { return providers; } - private getSupportedChainIds(chainsConfig: typeof supportedChains) { - const namespacesChains = Object.values(chainsConfig); - - return namespacesChains.reduce((acc, namespaceChains) => { - return [...acc, ...Object.values(namespaceChains.chains)]; - }, [] as string[]); + private isChainSupported(chainId: Caip2ChainId) { + return EBO_SUPPORTED_CHAIN_IDS.includes(chainId); } } diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index 2c7074f..55d9b77 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -1,10 +1,22 @@ -export const supportedChains = { +export const EBO_SUPPORTED_CHAINS_CONFIG = { evm: { namespace: "eip155", - chains: { - ethereum: "eip155:1", - polygon: "eip155:137", - arbitrum: "eip155:42161", + references: { + ethereum: "1", + polygon: "137", + arbitrum: "42161", }, }, } as const; + +export const EBO_SUPPORTED_CHAIN_IDS = Object.values(EBO_SUPPORTED_CHAINS_CONFIG).reduce( + (acc, namespace) => { + const namespaceReferences = Object.values(namespace.references); + const chainIds = namespaceReferences.map( + (reference) => `${namespace.namespace}:${reference}`, + ); + + return [...acc, ...chainIds]; + }, + [] as string[], +); From 927d237db6a8dbaaf9c52529a2eafef207ca92b7 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 2 Aug 2024 09:24:50 -0300 Subject: [PATCH 32/32] docs: document BlockNumberService and BlockNumberProviderFactory --- .../providers/blockNumberProviderFactory.ts | 8 +++++ .../src/services/blockNumberService.ts | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/packages/blocknumber/src/providers/blockNumberProviderFactory.ts b/packages/blocknumber/src/providers/blockNumberProviderFactory.ts index 3db5594..99360c0 100644 --- a/packages/blocknumber/src/providers/blockNumberProviderFactory.ts +++ b/packages/blocknumber/src/providers/blockNumberProviderFactory.ts @@ -12,6 +12,14 @@ const DEFAULT_PROVIDER_CONFIG = { }; export class BlockNumberProviderFactory { + /** + * Build a `BlockNumberProvider` to handle communication with the specified chain. + * + * @param chainId CAIP-2 chain id + * @param client a viem public client + * @param logger a ILogger instance + * @returns + */ public static buildProvider( chainId: Caip2ChainId, client: PublicClient>, diff --git a/packages/blocknumber/src/services/blockNumberService.ts b/packages/blocknumber/src/services/blockNumberService.ts index 94b084b..84b8aa1 100644 --- a/packages/blocknumber/src/services/blockNumberService.ts +++ b/packages/blocknumber/src/services/blockNumberService.ts @@ -11,6 +11,13 @@ type RpcUrl = NonNullable[0]>; export class BlockNumberService { private blockNumberProviders: Map; + /** + * Create a `BlockNumberService` instance that will handle the interaction with a collection + * of chains. + * + * @param chainRpcUrls a map of CAIP-2 chain ids with their RPC urls that this service will handle + * @param logger a `ILogger` instance + */ constructor( chainRpcUrls: Map, private readonly logger: ILogger, @@ -18,6 +25,13 @@ export class BlockNumberService { this.blockNumberProviders = this.buildBlockNumberProviders(chainRpcUrls); } + /** + * Get a chain epoch block number based on a timestamp. + * + * @param timestamp UTC timestamp in ms since UNIX epoch + * @param chainId the CAIP-2 chain id + * @returns the block number corresponding to the timestamp + */ public async getEpochBlockNumber(timestamp: number, chainId: Caip2ChainId): Promise { const provider = this.blockNumberProviders.get(chainId); @@ -28,6 +42,13 @@ export class BlockNumberService { return blockNumber; } + /** + * Get the epoch block number for all the specified chains based on a timestamp. + * + * @param timestamp UTC timestamp in ms since UNIX epoch + * @param chains a list of CAIP-2 chain ids + * @returns a map of CAIP-2 chain ids + */ public async getEpochBlockNumbers(timestamp: number, chains: Caip2ChainId[]) { const epochBlockNumbers = await Promise.all( chains.map(async (chain) => ({ @@ -41,6 +62,13 @@ export class BlockNumberService { }, new Map()); } + /** + * Build a collection of `BlockNumberProvider`s instances respective to each + * CAIP-2 chain id. + * + * @param chainRpcUrls a map containing chain ids with their respective list of RPC urls + * @returns a map of CAIP-2 chain ids and their respective `BlockNumberProvider` instances + */ private buildBlockNumberProviders(chainRpcUrls: Map) { if (chainRpcUrls.size == 0) throw new EmptyRpcUrls(); @@ -63,6 +91,12 @@ export class BlockNumberService { return providers; } + /** + * Check if a chain is supported by the service. + * + * @param chainId CAIP-2 chain id + * @returns true if the chain is supported, false otherwise + */ private isChainSupported(chainId: Caip2ChainId) { return EBO_SUPPORTED_CHAIN_IDS.includes(chainId); }