From 81a09ba50d1c13edce02ae1c93649f7b142fd016 Mon Sep 17 00:00:00 2001 From: Annie <168873935+AnnieIj@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:06:55 +0000 Subject: [PATCH 1/2] chore: add unified ESLint config and fix linting issues across monorepo Centralize ESLint in the root flat config (eslint.config.js) so every workspace package inherits the same rules: type-aware linting via each package's tsconfig, consistent type imports, and no-floating/misused promise enforcement. Add lint scripts to access-api and contracts, a root lint:fix script, Node globals for JS files, and a CI workflow that runs lint and typecheck. Fixes lint errors across the monorepo and keeps pre-existing build-order gaps working (contracts prepare script, @types/node for discord-bot). --- .github/workflows/ci.yml | 40 +++++++++++ apps/access-api/package.json | 1 + apps/access-api/test/indexer.test.ts | 2 +- apps/access-api/test/reorg.test.ts | 2 +- apps/dashboard/app/activity/page.tsx | 4 +- apps/dashboard/app/api/activity/route.ts | 2 +- apps/dashboard/app/api/guilds/route.ts | 2 +- apps/dashboard/app/api/integrations/route.ts | 2 +- apps/dashboard/app/api/members/route.ts | 2 +- apps/dashboard/app/api/passes/route.ts | 2 +- apps/dashboard/app/api/settings/route.ts | 2 +- apps/dashboard/app/api/verify/route.ts | 2 +- apps/dashboard/app/api/webhooks/route.ts | 2 +- apps/dashboard/app/dashboard/page.tsx | 4 +- apps/dashboard/app/guilds/page.tsx | 8 ++- apps/dashboard/app/integrations/page.tsx | 3 +- apps/dashboard/app/members/page.tsx | 64 +++++++++-------- apps/dashboard/app/passes/page.tsx | 68 ++++++++++--------- apps/dashboard/app/settings/page.tsx | 7 +- apps/dashboard/lib/activity/storage.ts | 5 +- apps/dashboard/lib/data/activity-service.ts | 4 +- apps/dashboard/lib/hooks/useApiList.ts | 3 +- .../lib/integrations/discord-live-adapter.ts | 2 +- .../lib/integrations/discord-mock-adapter.ts | 2 +- apps/dashboard/lib/integrations/index.ts | 2 +- apps/discord-bot/package.json | 3 +- eslint.config.js | 49 +++++++++++-- package.json | 1 + packages/contracts/package.json | 4 +- .../src/contracts/contract.types.ts | 2 +- .../src/contracts/contractClient.ts | 2 +- packages/webhook-utils/examples/express.ts | 3 +- packages/webhook-utils/examples/testing.ts | 2 +- packages/webhook-utils/src/verify.ts | 2 +- pnpm-lock.yaml | 5 +- 35 files changed, 205 insertions(+), 105 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..11974a0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-and-typecheck: + name: Lint & Typecheck + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint + + - name: Typecheck + run: pnpm typecheck diff --git a/apps/access-api/package.json b/apps/access-api/package.json index f2e17b3..495c727 100644 --- a/apps/access-api/package.json +++ b/apps/access-api/package.json @@ -9,6 +9,7 @@ "build": "tsc", "start": "node dist/index.js", "typecheck": "tsc --noEmit", + "lint": "eslint .", "prisma:generate": "prisma generate", "prisma:migrate": "prisma migrate dev", "test": "tsx --test test/**/*.test.ts" diff --git a/apps/access-api/test/indexer.test.ts b/apps/access-api/test/indexer.test.ts index ab85c1b..2b36f7d 100644 --- a/apps/access-api/test/indexer.test.ts +++ b/apps/access-api/test/indexer.test.ts @@ -1,4 +1,4 @@ -import { test, describe, beforeEach } from "node:test"; +import { test, describe } from "node:test"; import assert from "node:assert/strict"; import { MembershipIndexer } from "../src/workers/indexer.js"; diff --git a/apps/access-api/test/reorg.test.ts b/apps/access-api/test/reorg.test.ts index 7b3f2ae..38662c0 100644 --- a/apps/access-api/test/reorg.test.ts +++ b/apps/access-api/test/reorg.test.ts @@ -1,4 +1,4 @@ -import { test, describe, beforeEach } from "node:test"; +import { test, describe } from "node:test"; import assert from "node:assert/strict"; import { MembershipIndexer } from "../src/workers/indexer.js"; diff --git a/apps/dashboard/app/activity/page.tsx b/apps/dashboard/app/activity/page.tsx index c30cdd8..acba1e7 100644 --- a/apps/dashboard/app/activity/page.tsx +++ b/apps/dashboard/app/activity/page.tsx @@ -129,7 +129,7 @@ export default function ActivityPage() { diff --git a/apps/dashboard/app/passes/page.tsx b/apps/dashboard/app/passes/page.tsx index f5800f1..6020097 100644 --- a/apps/dashboard/app/passes/page.tsx +++ b/apps/dashboard/app/passes/page.tsx @@ -100,7 +100,7 @@ export default function PassesPage() { } } - load(); + void load(); return () => { mounted = false; }; @@ -164,13 +164,45 @@ export default function PassesPage() { }; const handleDeactivate = (id: string) => { - updateMutation.mutate({ id, data: { status: "inactive" } }); + // Errors are surfaced via onError (alert); avoid unhandled rejection + updateMutation.mutate({ id, data: { status: "inactive" } }).catch(() => {}); }; const handleEdit = (id: string) => { const name = prompt("Enter new name:"); if (name?.trim()) { - updateMutation.mutate({ id, data: { name: name.trim() } }); + // Errors are surfaced via onError (alert); avoid unhandled rejection + updateMutation.mutate({ id, data: { name: name.trim() } }).catch(() => {}); + } + }; + + const handleCreate = async () => { + if (!form.name.trim()) return alert("Pass name is required."); + if (!form.description.trim()) return alert("Description is required."); + + try { + setCreateLoading(true); + const res = await fetch("/api/passes", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: form.name.trim(), + description: form.description.trim(), + price: form.price ? Number(form.price) : undefined, + maxSupply: form.maxSupply ? Number(form.maxSupply) : null, + status: "draft", + currentSupply: 0, + }), + }); + const newPass = await readApiResult(res); + setPasses((prev) => [newPass, ...prev].slice(0, pagination.limit)); + setPagination((prev) => ({ ...prev, total: prev.total + 1 })); + setIsCreateOpen(false); + setForm({ name: "", description: "", price: "", maxSupply: "" }); + } catch (error: unknown) { + alert(error instanceof Error ? error.message : "Failed to create pass."); + } finally { + setCreateLoading(false); } }; @@ -247,35 +279,7 @@ export default function PassesPage() { diff --git a/apps/dashboard/app/settings/page.tsx b/apps/dashboard/app/settings/page.tsx index 60af313..376ab32 100644 --- a/apps/dashboard/app/settings/page.tsx +++ b/apps/dashboard/app/settings/page.tsx @@ -61,7 +61,7 @@ export default function SettingsPage() { } } - loadSettings(); + void loadSettings(); return () => { active = false; }; @@ -96,9 +96,10 @@ export default function SettingsPage() { } }); - async function handleSave(e: React.FormEvent) { + function handleSave(e: React.FormEvent) { e.preventDefault(); - saveMutation.mutate({ workspaceName, timezone, displayName, email }); + // Errors are surfaced via onError (alert); avoid unhandled rejection + saveMutation.mutate({ workspaceName, timezone, displayName, email }).catch(() => {}); } return ( diff --git a/apps/dashboard/lib/activity/storage.ts b/apps/dashboard/lib/activity/storage.ts index d5009ca..dcd407b 100644 --- a/apps/dashboard/lib/activity/storage.ts +++ b/apps/dashboard/lib/activity/storage.ts @@ -1,8 +1,9 @@ import { mkdir, open, readFile, appendFile, rm } from "node:fs/promises"; import { join } from "node:path"; -import { ActivityEvent } from "./types"; -import { ActivityQuery, ActivityQueryResult, filterActivityEvents } from "./query"; +import type { ActivityEvent } from "./types"; +import type { ActivityQuery, ActivityQueryResult } from "./query"; +import { filterActivityEvents } from "./query"; import { type Activity, mockActivity } from "../mock-data"; /** diff --git a/apps/dashboard/lib/data/activity-service.ts b/apps/dashboard/lib/data/activity-service.ts index b12b233..b6b510a 100644 --- a/apps/dashboard/lib/data/activity-service.ts +++ b/apps/dashboard/lib/data/activity-service.ts @@ -1,5 +1,5 @@ -import { ActivityEvent } from "@guildpass/integration-client"; -import { ActivityQuery, ActivityQueryResult } from "../activity/query"; +import type { ActivityEvent } from "@guildpass/integration-client"; +import type { ActivityQuery, ActivityQueryResult } from "../activity/query"; import { activityStorage } from "../activity/storage"; export interface ActivityStats { diff --git a/apps/dashboard/lib/hooks/useApiList.ts b/apps/dashboard/lib/hooks/useApiList.ts index 4a2be33..99b0796 100644 --- a/apps/dashboard/lib/hooks/useApiList.ts +++ b/apps/dashboard/lib/hooks/useApiList.ts @@ -129,7 +129,8 @@ export function useApiList({ useEffect(() => { mountedRef.current = true; - fetchData(); + // fetchData handles its own errors (state fallback), so no await needed + void fetchData(); return () => { mountedRef.current = false; }; diff --git a/apps/dashboard/lib/integrations/discord-live-adapter.ts b/apps/dashboard/lib/integrations/discord-live-adapter.ts index a41450c..5c850a4 100644 --- a/apps/dashboard/lib/integrations/discord-live-adapter.ts +++ b/apps/dashboard/lib/integrations/discord-live-adapter.ts @@ -1,4 +1,4 @@ -import { IntegrationAdapter, IntegrationDetails, IntegrationStatus } from "./types"; +import type { IntegrationAdapter, IntegrationDetails, IntegrationStatus } from "./types"; /** * Live adapter for the Discord Bot integration. diff --git a/apps/dashboard/lib/integrations/discord-mock-adapter.ts b/apps/dashboard/lib/integrations/discord-mock-adapter.ts index 7ab76e9..7deb084 100644 --- a/apps/dashboard/lib/integrations/discord-mock-adapter.ts +++ b/apps/dashboard/lib/integrations/discord-mock-adapter.ts @@ -1,4 +1,4 @@ -import { IntegrationAdapter, IntegrationDetails, IntegrationStatus } from "./types"; +import type { IntegrationAdapter, IntegrationDetails, IntegrationStatus } from "./types"; /** * Mock adapter for the Discord Bot integration. diff --git a/apps/dashboard/lib/integrations/index.ts b/apps/dashboard/lib/integrations/index.ts index 0896f96..c9a0068 100644 --- a/apps/dashboard/lib/integrations/index.ts +++ b/apps/dashboard/lib/integrations/index.ts @@ -1,4 +1,4 @@ -import { IntegrationAdapter, IntegrationDetails } from "./types"; +import type { IntegrationAdapter, IntegrationDetails } from "./types"; import { DiscordMockAdapter } from "./discord-mock-adapter"; import { DiscordLiveAdapter } from "./discord-live-adapter"; diff --git a/apps/discord-bot/package.json b/apps/discord-bot/package.json index 3b1fced..6238a52 100644 --- a/apps/discord-bot/package.json +++ b/apps/discord-bot/package.json @@ -20,7 +20,8 @@ }, "devDependencies": { "tsx": "^4.7.0", - "typescript": "^5.4.0" + "typescript": "^5.4.0", + "@types/node": "^20.17.6" }, "engines": { "node": ">=18.17.0" diff --git a/eslint.config.js b/eslint.config.js index 0c1cf83..1841d93 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,9 +6,10 @@ import { dirname } from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +// One shared ESLint config for the whole monorepo (flat config, ESLint >= 9). +// Every workspace package's `lint` script runs `eslint .` from its own +// directory and inherits these rules — no per-package configs needed. export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, { ignores: [ "**/dist/**", @@ -18,23 +19,49 @@ export default tseslint.config( "apps/docs/build/**" ] }, + eslint.configs.recommended, + ...tseslint.configs.recommended, { files: ["**/*.ts", "**/*.tsx"], languageOptions: { parser: tseslint.parser, parserOptions: { + // Type-aware linting: each workspace's tsconfig is registered so + // every linted file gets the compiler options of its own project. + // tsconfig.base.json is listed last as a fallback for files that are + // not included by a package tsconfig (e.g. package test directories). project: [ - "./tsconfig.base.json", + "./apps/access-api/tsconfig.json", + "./apps/dashboard/tsconfig.json", "./apps/discord-bot/tsconfig.json", + "./packages/contracts/tsconfig.json", "./packages/integration-client/tsconfig.json", - "./packages/webhook-utils/tsconfig.json" + "./packages/webhook-utils/tsconfig.json", + "./tsconfig.base.json" ], tsconfigRootDir: __dirname } }, rules: { + // Warnings by design — `any` is occasionally necessary (JSON-RPC wire + // types, mocks); keep it visible for review without blocking CI. "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + // Type-safety best practices: + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + "@typescript-eslint/no-non-null-assertion": "warn" + } + }, + { + // node:test's top-level `test()`/`describe()` calls (and test helpers in + // test directories) return promises that the test runner itself awaits — + // flagging them as floating is a false positive, so keep this rule strict + // for source code only. + files: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/test/**/*.ts"], + rules: { + "@typescript-eslint/no-floating-promises": "off" } }, { @@ -45,7 +72,17 @@ export default tseslint.config( module: "readonly", process: "readonly", __dirname: "readonly", - console: "readonly" + console: "readonly", + Buffer: "readonly", + URL: "readonly", + fetch: "readonly", + setTimeout: "readonly", + setInterval: "readonly", + clearTimeout: "readonly", + clearInterval: "readonly", + TextEncoder: "readonly", + TextDecoder: "readonly", + queueMicrotask: "readonly" } } } diff --git a/package.json b/package.json index 1c334df..d5acd8c 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "start": "pnpm --filter @guildpass/dashboard start", "typecheck": "pnpm -r typecheck", "lint": "pnpm -r lint", + "lint:fix": "eslint . --fix", "test": "pnpm -r test", "test:webhook-utils": "pnpm --filter @guildpass/webhook-utils test", "dev:bot": "pnpm --filter @guildpass/discord-bot dev", diff --git a/packages/contracts/package.json b/packages/contracts/package.json index d66d624..e4506f3 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -15,7 +15,9 @@ }, "scripts": { "build": "tsc -p tsconfig.json", - "typecheck": "tsc -p tsconfig.json --noEmit" + "prepare": "npm run build", + "typecheck": "tsc -p tsconfig.json --noEmit", + "lint": "eslint ." }, "devDependencies": { "typescript": "^5.4.0" diff --git a/packages/integration-client/src/contracts/contract.types.ts b/packages/integration-client/src/contracts/contract.types.ts index fdc2d29..1a03db7 100644 --- a/packages/integration-client/src/contracts/contract.types.ts +++ b/packages/integration-client/src/contracts/contract.types.ts @@ -18,4 +18,4 @@ export interface JsonRpcResponse { id: number | string; } -export interface ContractCallOptions extends HttpRequestOptions {} +export type ContractCallOptions = HttpRequestOptions; diff --git a/packages/integration-client/src/contracts/contractClient.ts b/packages/integration-client/src/contracts/contractClient.ts index 2d7feab..548f33a 100644 --- a/packages/integration-client/src/contracts/contractClient.ts +++ b/packages/integration-client/src/contracts/contractClient.ts @@ -1,4 +1,4 @@ -import { HttpClient } from "../http/httpClient.js"; +import type { HttpClient } from "../http/httpClient.js"; import type { ContractCallOptions, JsonRpcRequest, JsonRpcResponse } from "./contract.types.js"; export class ContractClient { diff --git a/packages/webhook-utils/examples/express.ts b/packages/webhook-utils/examples/express.ts index 9559688..00a11f3 100644 --- a/packages/webhook-utils/examples/express.ts +++ b/packages/webhook-utils/examples/express.ts @@ -7,7 +7,8 @@ * IMPORTANT: Use express.raw() middleware to preserve the raw body. */ -import express, { Request, Response } from "express"; +import express from "express"; +import type { Request, Response } from "express"; import { verifySignature } from "@guildpass/webhook-utils"; const app = express(); diff --git a/packages/webhook-utils/examples/testing.ts b/packages/webhook-utils/examples/testing.ts index 603368d..ab6dd82 100644 --- a/packages/webhook-utils/examples/testing.ts +++ b/packages/webhook-utils/examples/testing.ts @@ -267,7 +267,7 @@ test.describe('Webhook Integration', () => { // Run tests if executed directly if (require.main === module) { - (async () => { + void (async () => { console.log("Running webhook tests...\n"); testVerificationLogic(); diff --git a/packages/webhook-utils/src/verify.ts b/packages/webhook-utils/src/verify.ts index c3b77ff..75f0f4c 100644 --- a/packages/webhook-utils/src/verify.ts +++ b/packages/webhook-utils/src/verify.ts @@ -135,7 +135,7 @@ export function verifySignature(opts: VerifyOptions): VerifyResult { } return { valid: true, timestamp }; - } catch (err) { + } catch { return { valid: false, error: "Signature comparison failed", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4f9b70..0c4f8e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,9 @@ importers: specifier: ^16.4.5 version: 16.6.1 devDependencies: + '@types/node': + specifier: ^20.17.6 + version: 20.19.43 tsx: specifier: ^4.7.0 version: 4.22.4 @@ -7100,7 +7103,7 @@ snapshots: '@docusaurus/utils': 3.2.1(@docusaurus/types@3.2.1(postcss@8.5.15)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(clean-css@5.3.3)(cssnano@5.1.15(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15) '@docusaurus/utils-common': 3.2.1(@docusaurus/types@3.2.1(postcss@8.5.15)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)) '@types/history': 4.7.11 - '@types/react': 19.2.17 + '@types/react': 18.3.31 '@types/react-router-config': 5.0.11 clsx: 2.1.1 parse-numeric-range: 1.3.0 From c0f77ea135e97a27eed790e71d83bb643902be2f Mon Sep 17 00:00:00 2001 From: Annie <168873935+AnnieIj@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:03:03 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20make=20CI=20green=20=E2=80=94=20drop?= =?UTF-8?q?=20pnpm=20cache=20step=20and=20fix=20access-api=20typecheck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI failed at setup-node because actions/setup-node's pnpm cache requires a pnpm-lock.yaml, but lock files are untracked in this repo — remove the cache option. Also fix upstream's pre-existing access-api typecheck breakage (code referenced Prisma models missing from the committed schema): add the FailedEvent model and ProcessedEvent.previousState field to schema.prisma, and make applyEventApplication snapshot the pre-application membership state so previousState is actually populated. The dead-letter retry path (retryFailedEvent/getDeadLetterMetrics) now typechecks. --- .github/workflows/ci.yml | 3 ++- apps/access-api/prisma/schema.prisma | 18 ++++++++++++++ apps/access-api/src/workers/indexer.ts | 33 ++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a31e3d..eddaf07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,10 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - cache: pnpm - name: Install dependencies + # No lockfile is tracked (lock files are gitignored), so pnpm resolves + # versions from the workspace manifests. run: pnpm install - name: Lint diff --git a/apps/access-api/prisma/schema.prisma b/apps/access-api/prisma/schema.prisma index 15be37e..55799b1 100644 --- a/apps/access-api/prisma/schema.prisma +++ b/apps/access-api/prisma/schema.prisma @@ -17,6 +17,7 @@ model ProcessedEvent { status String @default("processed") // e.g., processed, reverted eventType String data Json + previousState Json? // pre-application state snapshot (used for exact rollback) fencingToken Int @default(0) // Monotonically increasing leader generation createdAt DateTime @default(now()) @@ -26,6 +27,23 @@ model ProcessedEvent { @@index([blockHash]) } +/// Dead-letter queue: logs that failed processing, retryable via the backfill CLI. +model FailedEvent { + id String @id @default(uuid()) + contractAddress String + blockHash String + blockNumber BigInt + transactionHash String + logIndex Int + eventType String + error String + data Json + retryCount Int @default(0) + createdAt DateTime @default(now()) + + @@index([contractAddress]) +} + model Membership { id String @id @default(uuid()) diff --git a/apps/access-api/src/workers/indexer.ts b/apps/access-api/src/workers/indexer.ts index 3c4c985..e02811e 100644 --- a/apps/access-api/src/workers/indexer.ts +++ b/apps/access-api/src/workers/indexer.ts @@ -279,21 +279,50 @@ export class IndexerCore { } } - private async applyEventApplication(decoded: any, tx: any) { + private async applyEventApplication( + decoded: any, + tx: any, + ): Promise<{ wallet: string; passId: string; status: number } | undefined> { const { eventName, args } = decoded; if (eventName === MEMBERSHIP_EVENTS.MembershipCreated) { + const previous = await tx.membership.findUnique({ + where: { wallet_passId: { wallet: args.member, passId: args.passId } }, + }); await tx.membership.upsert({ where: { wallet_passId: { wallet: args.member, passId: args.passId } }, update: { status: 1 }, create: { wallet: args.member, passId: args.passId, status: 1 }, }); - } else if (eventName === MEMBERSHIP_EVENTS.MembershipUpdated) { + // Snapshot the pre-application state so a reorg can roll back exactly + // (passId is BigInt in the DB, so serialise it for JSON storage). + return previous + ? { + wallet: previous.wallet, + passId: previous.passId.toString(), + status: previous.status, + } + : undefined; + } + + if (eventName === MEMBERSHIP_EVENTS.MembershipUpdated) { + const previous = await tx.membership.findUnique({ + where: { wallet_passId: { wallet: args.member, passId: args.passId } }, + }); await tx.membership.update({ where: { wallet_passId: { wallet: args.member, passId: args.passId } }, data: { status: args.newStatus }, }); + return previous + ? { + wallet: previous.wallet, + passId: previous.passId.toString(), + status: previous.status, + } + : undefined; } + + return undefined; } private async revertEventApplication(event: any, tx: any) {