From 2c68d9ab1b9b9e6997a494845c0e19847e93e026 Mon Sep 17 00:00:00 2001 From: VGabriel45 Date: Thu, 11 Dec 2025 17:55:09 +0200 Subject: [PATCH 1/3] remove recursive Types not compatible with OpenAPI --- package.json | 2 +- .../utils/debug/checkForInternalReverts.ts | 32 +++++++++---------- .../contract/utils/debug/getDecodedTrace.ts | 32 +++++++++---------- 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index f0c5a2a..0001542 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sequence_sidekick", - "version": "v1.0.17-beta", + "version": "v1.0.19-beta", "module": "index.ts", "type": "module", "scripts": { diff --git a/src/routes/contract/utils/debug/checkForInternalReverts.ts b/src/routes/contract/utils/debug/checkForInternalReverts.ts index 4018e7e..02ed834 100644 --- a/src/routes/contract/utils/debug/checkForInternalReverts.ts +++ b/src/routes/contract/utils/debug/checkForInternalReverts.ts @@ -64,23 +64,21 @@ const decodedSignatureSchema = Type.Object({ ) }) -const simplifiedTraceCallSchema = Type.Recursive((Self) => - Type.Object({ - type: Type.String(), - from: Type.String(), - fromContractName: Type.String(), - to: Type.String(), - toContractName: Type.String(), - functionSelector: Type.String(), - decodedFunctionSelector: decodedSignatureSchema, - value: Type.String(), - valueInEther: Type.String(), - gasUsed: Type.String(), - reverted: Type.Boolean(), - revertReason: Type.Optional(Type.String()), - calls: Type.Array(Self) - }) -) +const simplifiedTraceCallSchema = Type.Object({ + type: Type.String(), + from: Type.String(), + fromContractName: Type.String(), + to: Type.String(), + toContractName: Type.String(), + functionSelector: Type.String(), + decodedFunctionSelector: decodedSignatureSchema, + value: Type.String(), + valueInEther: Type.String(), + gasUsed: Type.String(), + reverted: Type.Boolean(), + revertReason: Type.Optional(Type.String()), + calls: Type.Array(Type.Any()) +}) const debugCheckIfRevertedSchema = { description: diff --git a/src/routes/contract/utils/debug/getDecodedTrace.ts b/src/routes/contract/utils/debug/getDecodedTrace.ts index fc9a63c..00aba31 100644 --- a/src/routes/contract/utils/debug/getDecodedTrace.ts +++ b/src/routes/contract/utils/debug/getDecodedTrace.ts @@ -61,23 +61,21 @@ const decodedSignatureSchema = Type.Object({ ) }) -const decodedTraceCallSchema = Type.Recursive((Self) => - Type.Object({ - type: Type.String(), - from: Type.String(), - fromContractName: Type.String(), - to: Type.String(), - toContractName: Type.String(), - functionSelector: Type.String(), - decodedFunctionSelector: decodedSignatureSchema, - value: Type.String(), - valueInEther: Type.String(), - gasUsed: Type.String(), - reverted: Type.Boolean(), - revertReason: Type.Optional(Type.String()), - calls: Type.Array(Self) - }) -) +const decodedTraceCallSchema = Type.Object({ + type: Type.String(), + from: Type.String(), + fromContractName: Type.String(), + to: Type.String(), + toContractName: Type.String(), + functionSelector: Type.String(), + decodedFunctionSelector: decodedSignatureSchema, + value: Type.String(), + valueInEther: Type.String(), + gasUsed: Type.String(), + reverted: Type.Boolean(), + revertReason: Type.Optional(Type.String()), + calls: Type.Array(Type.Any()) +}) const decodedTraceSchema = { description: From 723f3ca455304dd69fe34a929aa52b24d1eb6ae9 Mon Sep 17 00:00:00 2001 From: VGabriel45 Date: Thu, 11 Dec 2025 19:01:06 +0200 Subject: [PATCH 2/3] add script to check OpenAPI compatibility + fixes --- package.json | 2 + pnpm-lock.yaml | 67 ++++++++ scripts/check-openapi.ts | 150 ++++++++++++++++++ .../contract/utils/debug/getRawTrace.ts | 15 +- .../utils/relayer/getTxHashForMetaTxHash.ts | 10 +- .../contract/utils/relayer/getTxReceipt.ts | 41 ++++- 6 files changed, 275 insertions(+), 10 deletions(-) create mode 100644 scripts/check-openapi.ts diff --git a/package.json b/package.json index 0001542..df829ee 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,11 @@ "lint": "biome check .", "lint:fix": "biome check . --write", "tag:release": "sh ./scripts/tag-release.sh", + "check:openapi": "tsx scripts/check-openapi.ts", "postinstall": "prisma generate" }, "devDependencies": { + "@apidevtools/swagger-parser": "^10.1.1", "@biomejs/biome": "1.9.4", "@types/js-yaml": "^4.0.9", "@types/node": "^22.13.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c94e16a..af79dab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,6 +114,9 @@ importers: specifier: ^3.0.5 version: 3.2.4(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5)(yaml@2.8.1) devDependencies: + '@apidevtools/swagger-parser': + specifier: ^10.1.1 + version: 10.1.1(openapi-types@12.1.3) '@biomejs/biome': specifier: 1.9.4 version: 1.9.4 @@ -299,6 +302,22 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@apidevtools/json-schema-ref-parser@11.7.2': + resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==} + engines: {node: '>= 16'} + + '@apidevtools/openapi-schemas@2.1.0': + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + + '@apidevtools/swagger-methods@3.0.2': + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + + '@apidevtools/swagger-parser@10.1.1': + resolution: {integrity: sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==} + peerDependencies: + openapi-types: '>=7' + '@aws-crypto/sha256-browser@5.2.0': resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} @@ -775,6 +794,9 @@ packages: '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} @@ -1216,6 +1238,9 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} @@ -1301,6 +1326,14 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -1406,6 +1439,9 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -2802,6 +2838,27 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.30 + '@apidevtools/json-schema-ref-parser@11.7.2': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.0 + + '@apidevtools/openapi-schemas@2.1.0': {} + + '@apidevtools/swagger-methods@3.0.2': {} + + '@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3)': + dependencies: + '@apidevtools/json-schema-ref-parser': 11.7.2 + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@jsdevtools/ono': 7.1.3 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + call-me-maybe: 1.0.2 + openapi-types: 12.1.3 + '@aws-crypto/sha256-browser@5.2.0': dependencies: '@aws-crypto/sha256-js': 5.2.0 @@ -3498,6 +3555,8 @@ snapshots: '@js-sdsl/ordered-map@4.4.2': {} + '@jsdevtools/ono@7.1.3': {} + '@lukeed/ms@2.0.2': {} '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': @@ -3987,6 +4046,8 @@ snapshots: '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} + '@types/long@4.0.2': {} '@types/node@22.18.1': @@ -4088,6 +4149,10 @@ snapshots: agent-base@7.1.4: {} + ajv-draft-04@1.0.0(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -4190,6 +4255,8 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-me-maybe@1.0.2: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 diff --git a/scripts/check-openapi.ts b/scripts/check-openapi.ts new file mode 100644 index 0000000..bdf99d1 --- /dev/null +++ b/scripts/check-openapi.ts @@ -0,0 +1,150 @@ +#!/usr/bin/env tsx +/** + * Script to validate OpenAPI specification compatibility + * Fetches the OpenAPI spec from a running server and validates it + */ + +import SwaggerParser from '@apidevtools/swagger-parser' + +// Get server URL from environment or use default +const SERVER_URL = process.env.SERVER_URL || 'http://localhost:7500' + +// Common OpenAPI endpoint paths +const POSSIBLE_ENDPOINTS = [ + '/documentation/json', // Fastify Swagger UI default + '/openapi.json', + '/swagger.json', + '/documentation/openapi.json' +] + +async function fetchOpenAPISpec(): Promise> { + let lastError: Error | null = null + + for (const endpoint of POSSIBLE_ENDPOINTS) { + const url = `${SERVER_URL}${endpoint}` + try { + const response = await fetch(url) + if (response.ok) { + const spec = (await response.json()) as Record + console.log(`✓ Found OpenAPI spec at: ${url}\n`) + return spec + } + } catch (error) { + if (error instanceof Error) { + lastError = error + } + // Try next endpoint + continue + } + } + + // If we get here, none of the endpoints worked + if (lastError && lastError.message.includes('fetch failed')) { + throw new Error( + `Could not connect to server at ${SERVER_URL}. Make sure the server is running.\n` + + `You can start it with: pnpm run dev` + ) + } + + throw new Error( + `Could not find OpenAPI spec at any of the expected endpoints:\n` + + POSSIBLE_ENDPOINTS.map((ep) => ` - ${SERVER_URL}${ep}`).join('\n') + + `\n\nMake sure the server is running and Swagger is properly configured.` + ) +} + +async function validateOpenAPI(spec: Record): Promise { + try { + // Validate the spec structure and references + // Cast to any to satisfy SwaggerParser's type requirements + const api = await SwaggerParser.validate(spec as any, { + validate: { + spec: true, + schema: true + }, + dereference: { + circular: false + } + }) + + // Type guard for API document + const apiDoc = api as unknown as Record + const info = apiDoc.info as Record | undefined + const paths = apiDoc.paths as Record | undefined + + console.log('✅ OpenAPI specification is valid!') + console.log(` Title: ${info?.title || 'N/A'}`) + console.log(` Version: ${info?.version || 'N/A'}`) + console.log( + ` OpenAPI Version: ${apiDoc.openapi || apiDoc.swagger || 'N/A'}` + ) + console.log(` Paths: ${Object.keys(paths || {}).length}`) + + // Check for common issues + const warnings: string[] = [] + + if (!paths || Object.keys(paths).length === 0) { + warnings.push('⚠️ No API paths defined') + } + + // Check for missing response schemas + for (const [path, pathItem] of Object.entries(paths || {})) { + if (!pathItem) continue + for (const [method, operation] of Object.entries(pathItem)) { + if ( + typeof operation === 'object' && + operation !== null && + 'responses' in operation + ) { + const responses = operation.responses + if (!responses || Object.keys(responses).length === 0) { + warnings.push( + `⚠️ ${method.toUpperCase()} ${path} has no response definitions` + ) + } + } + } + } + + if (warnings.length > 0) { + console.log('\n⚠️ Warnings:') + warnings.forEach((warning) => console.log(` ${warning}`)) + } + + console.log('\n✅ All validation checks passed!') + } catch (error) { + if (error instanceof Error) { + console.error('❌ OpenAPI validation failed:') + console.error(` ${error.message}`) + if ('details' in error && Array.isArray(error.details)) { + console.error('\n Details:') + error.details.forEach((detail: unknown) => { + if (typeof detail === 'object' && detail !== null) { + console.error(` - ${JSON.stringify(detail, null, 2)}`) + } + }) + } + } else { + console.error('❌ Unknown validation error:', error) + } + process.exit(1) + } +} + +async function main() { + console.log(`🔍 Validating OpenAPI specification from ${SERVER_URL}...\n`) + + try { + const spec = await fetchOpenAPISpec() + await validateOpenAPI(spec) + } catch (error) { + if (error instanceof Error) { + console.error('❌ Error:', error.message) + } else { + console.error('❌ Unknown error:', error) + } + process.exit(1) + } +} + +main() diff --git a/src/routes/contract/utils/debug/getRawTrace.ts b/src/routes/contract/utils/debug/getRawTrace.ts index f049e47..ceb8c36 100644 --- a/src/routes/contract/utils/debug/getRawTrace.ts +++ b/src/routes/contract/utils/debug/getRawTrace.ts @@ -32,19 +32,28 @@ const debugTraceTransactionSchema = { response: { 200: Type.Object({ result: Type.Object({ - trace: Type.Any(), + trace: { + type: 'object', + nullable: true + } as any, error: Type.Optional(Type.String()) }) }), '4xx': Type.Object({ result: Type.Object({ - trace: Type.Null(), + trace: { + type: 'object', + nullable: true + } as any, error: Type.String() }) }), 500: Type.Object({ result: Type.Object({ - trace: Type.Null(), + trace: { + type: 'object', + nullable: true + } as any, error: Type.String() }) }) diff --git a/src/routes/contract/utils/relayer/getTxHashForMetaTxHash.ts b/src/routes/contract/utils/relayer/getTxHashForMetaTxHash.ts index eb5d5fc..96573ab 100644 --- a/src/routes/contract/utils/relayer/getTxHashForMetaTxHash.ts +++ b/src/routes/contract/utils/relayer/getTxHashForMetaTxHash.ts @@ -32,7 +32,10 @@ const getTxHashForMetaTxHashSchema = { 200: Type.Object({ result: Type.Object({ data: Type.Object({ - txHash: Type.String() + txHash: { + type: 'string', + nullable: true + } as any }) }), error: Type.Optional(Type.String()) @@ -40,7 +43,10 @@ const getTxHashForMetaTxHashSchema = { 500: Type.Object({ result: Type.Object({ data: Type.Object({ - txHash: Type.Null() + txHash: { + type: 'string', + nullable: true + } as any }), error: Type.String() }) diff --git a/src/routes/contract/utils/relayer/getTxReceipt.ts b/src/routes/contract/utils/relayer/getTxReceipt.ts index bf3daec..5238043 100644 --- a/src/routes/contract/utils/relayer/getTxReceipt.ts +++ b/src/routes/contract/utils/relayer/getTxReceipt.ts @@ -86,10 +86,21 @@ const getTxReceiptSchema = { blockNumber: Type.String(), transactionIndex: Type.String() }), - hasRevertedCalls: Type.Union([Type.Boolean(), Type.Null()]), - revertedCalls: Type.Union([Type.Array(Type.Any()), Type.Null()]), + hasRevertedCalls: { + type: 'boolean', + nullable: true + } as any, + revertedCalls: { + type: 'array', + items: {}, + nullable: true + } as any, isSuccessful: Type.Boolean(), - revertReasons: Type.Union([Type.Array(Type.String()), Type.Null()]) + revertReasons: { + type: 'array', + items: { type: 'string' }, + nullable: true + } as any }), error: Type.Optional(Type.String()) }) @@ -97,8 +108,28 @@ const getTxReceiptSchema = { 500: Type.Object({ result: Type.Object({ data: Type.Object({ - receipt: Type.Null(), - isSuccessful: Type.Null() + receipt: { + type: 'object', + nullable: true + } as any, + isSuccessful: { + type: 'boolean', + nullable: true + } as any, + hasRevertedCalls: { + type: 'boolean', + nullable: true + } as any, + revertedCalls: { + type: 'array', + items: {}, + nullable: true + } as any, + revertReasons: { + type: 'array', + items: { type: 'string' }, + nullable: true + } as any }), error: Type.String() }) From d96dc7a0f018bf610bf72a5e12fef4de8771e992 Mon Sep 17 00:00:00 2001 From: VGabriel45 Date: Thu, 11 Dec 2025 19:03:57 +0200 Subject: [PATCH 3/3] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df829ee..a4ffda1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sequence_sidekick", - "version": "v1.0.19-beta", + "version": "v1.0.20-beta", "module": "index.ts", "type": "module", "scripts": {