From bce19b4eb00878586494bd43fd0e14abae49a6c1 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Wed, 13 Dec 2023 14:11:21 +0100 Subject: [PATCH 01/11] Fix typos --- CONTRIBUTING.MD | 2 +- README.md | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD index 3a6dd7e..8e76901 100644 --- a/CONTRIBUTING.MD +++ b/CONTRIBUTING.MD @@ -14,7 +14,7 @@ 1. Run `pnpm install` in the root of the repository to install all dependencies. 2. Run `pnpm build` to create an initial build of the `explorerkit-idls` and `explorerkit-translator` 3. You should now be able to edit the source code to your liking and run `pnpm dev` to ensure that your changes are being watched -4. After you are dong with your changes, you can write test cases in the `tests` folder of the root of the package to ensure that your changes work as expected. You can run `pnpm test` in the root of the repository to run all the tests in the repository. +4. After you are done with your changes, you can write test cases in the `tests` folder of the root of the package to ensure that your changes work as expected. You can run `pnpm test` in the root of the repository to run all the tests in the repository. ## Running Tests diff --git a/README.md b/README.md index 812ed39..24ef8d1 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ const SFMIdlItem = await getProgramIdl(programId); // You can also get an IDL at a specific slot context if you're trying to histroically parse a transaction / account // but the IDL might not be backwards compatible. const historicalSFMIdlItem = await getProgramIdl(programId, { - slotContext: 132322893, + slotContext: 132322893, }); ``` @@ -105,7 +105,7 @@ Parsing an event data: ```ts import { SolanaFMParser. checkIfEventParser, ParserType } from "@solanafm/explorer-kit" -// For event data they have to base-64 encoded and they can be extracted from logs or a inner instruction with CPI logs. +// For event data they have to base-64 encoded and they can be extracted from logs or a inner instruction with CPI logs. // Phoenix Program Event Data const eventData = "DwEABF2SDQAAAABDfDtlAAAAAKiVfA0AAAAAL9p3EN7QVm+wCbiCUn2jVyJyazsZQYgqVRhf6h2a/pX5SjR+9eBu2sQU7NYr1TEeH7vRFNOiXSyDLJ9g+fDJrwMAAgAABPzrK7CsLqR5NiVFXYwyp7QgatDNQXbn3JA8wOVXQfANFxMTAAAAAIB/AAAAAAAAg7MAAAAAAAAAAAAAAAAAAAIBAAT86yuwrC6keTYlRV2MMqe0IGrQzUF259yQPMDlV0HwDhcTEwAAAACCfwAAAAAAAByBAAAAAAAA6EwCAAAAAAAGAgAAAAAAAAAAAAAAAAAAAAAAnzQBAAAAAABzEb6ZAAAAALveBwAAAAAA" const parser = new SolanaFMParser(SFMIdlItem); @@ -120,18 +120,21 @@ if (eventParser && checkIfEventParser(eventParser)) { Parsing an account data: ```ts -import { SolanaFMParser. checkIfAccountParser, ParserType } from "@solanafm/explorer-kit" +import { SolanaFMParser, checkIfAccountParser, ParserType } from "@solanafm/explorer-kit"; + +const SFMIdlItem = await getProgramIdl(programId); // Account data have to be base-64 encoded // Stake Pool Account Data -const accountData = "AWq1iyr99ATwNekhxZcljopQjeBixmWt+p/5CTXBmRbd3Noj1MlCDU6CVh08awajdvCUB/G3tPyo/emrHFdD8Wfh4Pippvxf8kLk81F78B7Wst0ZUaC6ttlDVyWShgT3cP/LqkIDCUdVLBkThURwDuYX1RR+JyWBHNvgnIkDCm914o2jckW1NrCzDbv9Jn/RWcT0cAMYKm8U4SfG/F878wV0XwxEYxirEMlfQJSVhXDNBXRlpU2rFNnd40gahv7V/Mvj/aPav/vdTOwRdFALTRZQlijB9G5myz+0QWe7U7EGIQbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpE2P1ZIWKAQDUAp5GdmQBAMkBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQJwAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECcAAAAAAAAAAAAAAAAAAABWHWK1dGQBAAgnQqFYigEAv0rw1gHIAQAPfXpGLPQBABAnAAAAAAAAAAAAAAAAAAAAicd7jscBANVMdCNW7gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; +const accountData = + "AWq1iyr99ATwNekhxZcljopQjeBixmWt+p/5CTXBmRbd3Noj1MlCDU6CVh08awajdvCUB/G3tPyo/emrHFdD8Wfh4Pippvxf8kLk81F78B7Wst0ZUaC6ttlDVyWShgT3cP/LqkIDCUdVLBkThURwDuYX1RR+JyWBHNvgnIkDCm914o2jckW1NrCzDbv9Jn/RWcT0cAMYKm8U4SfG/F878wV0XwxEYxirEMlfQJSVhXDNBXRlpU2rFNnd40gahv7V/Mvj/aPav/vdTOwRdFALTRZQlijB9G5myz+0QWe7U7EGIQbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpE2P1ZIWKAQDUAp5GdmQBAMkBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQJwAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECcAAAAAAAAAAAAAAAAAAABWHWK1dGQBAAgnQqFYigEAv0rw1gHIAQAPfXpGLPQBABAnAAAAAAAAAAAAAAAAAAAAicd7jscBANVMdCNW7gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; const parser = new SolanaFMParser(SFMIdlItem); const eventParser = parser.createParser(ParserType.ACCOUNT); if (eventParser && checkIfAccountParser(eventParser)) { - // Parse the transaction - const decodedData = parser.parseAccount(accountData); + // Parse the transaction + const decodedData = eventParser.parseAccount(accountData); } ``` @@ -141,12 +144,11 @@ Once the data is parsed, the returned data type will look something like this export type ParserOutput = { // The name of the struct that's being used to parse the data name: string; - // The parsed data according to the IDL schema + // The parsed data according to the IDL schema data: any; - // ParserType depends on the type of parser you have initialized + // ParserType depends on the type of parser you have initialized type: ParserType; } | null; - ``` More to be added soon... @@ -161,8 +163,8 @@ You can also checkout the [examples](https://github.com/solana-fm/explorer-kit/e ## Supported Programs - | Program IDs | Program | Working Parsers | -|-----------------------------------------------------------------------------------------|--------------------------------|----------------------------------------------| +| Program IDs | Program | Working Parsers | +| --------------------------------------------------------------------------------------- | ------------------------------ | -------------------------------------------- | | 11111111111111111111111111111111 | System Program | Account, Instructions | | Config1111111111111111111111111111111111111 | Config Program | Account, Instructions | | Stake11111111111111111111111111111111111111 | Stake Program | Account, Instructions | From f8f6498a778c507da464f9ff84706331c2920222 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Wed, 13 Dec 2023 15:36:40 +0100 Subject: [PATCH 02/11] Initial implementation of exporer kit server that decode accounts & transactions --- README.md | 6 +- packages/explorerkit-server/.eslintrc.js | 5 + packages/explorerkit-server/README.md | 1 + packages/explorerkit-server/package.json | 52 ++ packages/explorerkit-server/src/index.ts | 166 +++++ packages/explorerkit-server/tsconfig.json | 7 + packages/explorerkit-server/vitest.config.ts | 11 + pnpm-lock.yaml | 638 +++++++++++++++++-- 8 files changed, 818 insertions(+), 68 deletions(-) create mode 100644 packages/explorerkit-server/.eslintrc.js create mode 100644 packages/explorerkit-server/README.md create mode 100644 packages/explorerkit-server/package.json create mode 100644 packages/explorerkit-server/src/index.ts create mode 100644 packages/explorerkit-server/tsconfig.json create mode 100644 packages/explorerkit-server/vitest.config.ts diff --git a/README.md b/README.md index 24ef8d1..3b1a1d3 100644 --- a/README.md +++ b/README.md @@ -90,12 +90,12 @@ const historicalSFMIdlItem = await getProgramIdl(programId, { const ixData = "1AMTAauCh9UPEJKKd6LnGGtWqFvRs2aUZkv9r6wNe3PTzB1KS9TbwYzM8Cp7vUSDYZXTxXJp5M" // Checks if SFMIdlItem is defined, if not you will not be able to initialize the parser layout if (SFMIdlItem) { - const parser = new SolanaFMParser(SFMIdlItem); - const instructionParser = parser.createParser(ParserType.INSTRUCTION); + const parser = new SolanaFMParser(SFMIdlItem, programId); + const instructionParser = instructionParser.createParser(ParserType.INSTRUCTION); if (instructionParser && checkIfInstructionParser(instructionParser)) { // Parse the transaction - const decodedData = parser.parseTransaction(ixData); + const decodedData = parser.parseInstructions(ixData); } } ``` diff --git a/packages/explorerkit-server/.eslintrc.js b/packages/explorerkit-server/.eslintrc.js new file mode 100644 index 0000000..5e1d4ee --- /dev/null +++ b/packages/explorerkit-server/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + root: true, + // This tells ESLint to load the config from the package `eslint-config-explorerkit` + extends: ["explorerkit"], +}; diff --git a/packages/explorerkit-server/README.md b/packages/explorerkit-server/README.md new file mode 100644 index 0000000..9a81b0e --- /dev/null +++ b/packages/explorerkit-server/README.md @@ -0,0 +1 @@ +# Explorer Kit Server diff --git a/packages/explorerkit-server/package.json b/packages/explorerkit-server/package.json new file mode 100644 index 0000000..0c3b0a3 --- /dev/null +++ b/packages/explorerkit-server/package.json @@ -0,0 +1,52 @@ +{ + "name": "@solanafm/explorer-kit-server", + "version": "1.0.0", + "description": "Server to decode Solana entities over HTTP API", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "/dist" + ], + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts", + "dev": "tsup src/index.ts --format esm,cjs --dts --watch", + "serve": "nodemon dist/index.js", + "test": "vitest run", + "clean": "rimraf .turbo && rimraf node_modules && rimraf dist", + "lint": "TIMING=1 eslint \"src/**/*.ts*\"", + "lint:fix": "TIMING=1 eslint \"src/**/*.ts*\" --fix", + "publish-package": "pnpm build && npm publish --access=public" + }, + "keywords": [], + "author": "SolanaFM", + "license": "GPL-3.0-or-later", + "devDependencies": { + "@types/body-parser": "^1.19.5", + "@types/express": "^4.17.21", + "@types/node": "^20.10.4", + "@vitest/coverage-v8": "^0.34.2", + "eslint": "^8.47.0", + "eslint-config-explorerkit": "workspace:*", + "nodemon": "^3.0.2", + "vitest": "^0.34.2" + }, + "dependencies": { + "@coral-xyz/anchor": "^0.27.0", + "@solana/web3.js": "^1.87.2", + "@solanafm/explorer-kit": "workspace:*", + "@solanafm/explorer-kit-idls": "workspace:*", + "@solanafm/kinobi-lite": "^0.12.0", + "axios": "^1.3.3", + "body-parser": "^1.20.2", + "bs58": "^5.0.0", + "express": "^4.18.2" + } +} diff --git a/packages/explorerkit-server/src/index.ts b/packages/explorerkit-server/src/index.ts new file mode 100644 index 0000000..9949351 --- /dev/null +++ b/packages/explorerkit-server/src/index.ts @@ -0,0 +1,166 @@ +import express, { Request, Response } from "express"; +import bodyParser from "body-parser"; +import { SolanaFMParser, checkIfAccountParser, checkIfInstructionParser, ParserType } from "@solanafm/explorer-kit"; +import { getProgramIdl } from "@solanafm/explorer-kit-idls"; +import { VersionedTransaction, Message, MessageV0, PublicKey } from "@solana/web3.js"; +import bs58 from "bs58"; + +interface Account { + ownerProgram: string; + data: string; +} + +interface DecodeAccountsRequestBody { + accounts: Account[]; +} + +interface DecodedAccount { + error: string | null; + decodedData: any | null; +} + +interface DecodeTransactionsRequestBody { + transactions: string[]; +} + +interface DecodedTransactions { + error: string | null; + decodedInstructions: any[] | null; +} + +interface GenericInstruction { + programId: string; + data: Uint8Array; +} + +const app = express(); +app.use(bodyParser.json()); + +// Endpoint to decode accounts data +app.post("/decode/accounts", async (req: Request, res: Response) => { + const { accounts } = req.body as DecodeAccountsRequestBody; + + let decodedAccounts: DecodedAccount[] = []; + for (var account of accounts) { + if (!isValidBase58(account.ownerProgram)) { + decodedAccounts.push({ error: "'account.ownerProgram' is not a valid base58 string.", decodedData: null }); + continue; + } + if (!isValidBase64(account.data)) { + decodedAccounts.push({ error: "'account.data' is not a valid base64 string.", decodedData: null }); + continue; + } + + const SFMIdlItem = await getProgramIdl(account.ownerProgram); + if (SFMIdlItem === null) { + decodedAccounts.push({ error: "Failed to find program IDL", decodedData: null }); + continue; + } + + const parser = new SolanaFMParser(SFMIdlItem, account.ownerProgram); + const eventParser = parser.createParser(ParserType.ACCOUNT); + + if (eventParser && checkIfAccountParser(eventParser)) { + // Parse the transaction + const decodedData = eventParser.parseAccount(account.data); + decodedAccounts.push({ error: null, decodedData }); + continue; + } else { + decodedAccounts.push({ error: "Failed to parse account", decodedData: null }); + continue; + } + } + + return res.status(200).json({ decodedAccounts }); +}); + +// // Endpoint to decode transactions +app.post("/decode/transactions", async (req: Request, res: Response) => { + const { transactions } = req.body as DecodeTransactionsRequestBody; + + let decodedAccounts: DecodedTransactions[] = []; + for (var encodedTx of transactions) { + let txBuffer = null; + if (isValidBase64(encodedTx)) { + txBuffer = Buffer.from(encodedTx, "base64"); + } else if (isValidBase58(encodedTx)) { + txBuffer = Buffer.from(bs58.decode(encodedTx)); + } else { + decodedAccounts.push({ error: "'transaction' is not a valid base64 string.", decodedInstructions: null }); + continue; + } + + try { + const tx = VersionedTransaction.deserialize(txBuffer); + let instructions: GenericInstruction[] = []; + if (tx.message instanceof Message) { + for (var ix of tx.message.instructions) { + instructions.push({ + programId: tx.message.accountKeys[ix.programIdIndex]?.toString() as string, // TODO(fabio): Fix me + data: bs58.decode(ix.data), + }); + } + } else if (tx.message instanceof MessageV0) { + for (var inst of tx.message.compiledInstructions) { + instructions.push({ + programId: (tx.message.staticAccountKeys[inst.programIdIndex] as PublicKey).toString(), // TODO(fabio): Fix me + data: inst.data, + }); + } + } else { + throw new Error("Unsupported message version"); + } + + let decodedInstructions: any[] = []; + for (var instruction of instructions) { + const programId = instruction.programId.toString(); + + const SFMIdlItem = await getProgramIdl(programId); + if (SFMIdlItem) { + const parser = new SolanaFMParser(SFMIdlItem, programId); + const instructionParser = parser.createParser(ParserType.INSTRUCTION); + + if (instructionParser && checkIfInstructionParser(instructionParser)) { + // Parse the transaction + const decodedInstruction = instructionParser.parseInstructions(bs58.encode(instruction.data)); + decodedInstructions.push(decodedInstruction); + } + } else { + decodedAccounts.push({ error: "Failed to find program IDL", decodedInstructions: null }); + } + } + + decodedAccounts.push({ error: null, decodedInstructions }); + } catch (e: any) { + decodedAccounts.push({ error: e.message, decodedInstructions: null }); + } + } + + return res.status(200).json({ decodedAccounts }); +}); + +// Function to validate base58 string +function isValidBase58(str: string): boolean { + const base58Regex = /^[A-HJ-NP-Za-km-z1-9]+$/; + return base58Regex.test(str); +} + +function isValidBase64(str: string): boolean { + try { + // Decode the string from Base64 and then re-encode it + const base64Encoded = Buffer.from(str, "base64").toString("base64"); + + // Check if the re-encoded string matches the original + // This ensures that the input was valid Base64 + return str === base64Encoded; + } catch (e) { + // If an error occurs during decoding, the string was not valid Base64 + return false; + } +} + +// Start the server +const PORT = 3000; +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); diff --git a/packages/explorerkit-server/tsconfig.json b/packages/explorerkit-server/tsconfig.json new file mode 100644 index 0000000..5951adb --- /dev/null +++ b/packages/explorerkit-server/tsconfig.json @@ -0,0 +1,7 @@ +{ + // We extend it from here! + "extends": "tsconfig/base.json", + // You can specify your own include/exclude + "include": ["src/**/*", "test/**/*"], + "exclude": ["**/node_modules", "**/*.js", "**/build/**/*", "**/dist/**/*"] +} \ No newline at end of file diff --git a/packages/explorerkit-server/vitest.config.ts b/packages/explorerkit-server/vitest.config.ts new file mode 100644 index 0000000..1f4a9c1 --- /dev/null +++ b/packages/explorerkit-server/vitest.config.ts @@ -0,0 +1,11 @@ +// vitest.config.ts +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, // <-- ** + coverage: { + reporter: ["text", "html"], // <-- *** + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea35a06..8b336e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 7.2.0 turbo: specifier: latest - version: 1.10.16 + version: 1.11.2 packages/eslint-config-explorerkit: dependencies: @@ -73,6 +73,61 @@ importers: specifier: ^0.34.2 version: 0.34.2 + packages/explorerkit-server: + dependencies: + '@coral-xyz/anchor': + specifier: ^0.27.0 + version: 0.27.0 + '@solana/web3.js': + specifier: ^1.87.2 + version: 1.87.2 + '@solanafm/explorer-kit': + specifier: workspace:* + version: link:../explorerkit-translator + '@solanafm/explorer-kit-idls': + specifier: workspace:* + version: link:../explorerkit-idls + '@solanafm/kinobi-lite': + specifier: ^0.12.0 + version: 0.12.0 + axios: + specifier: ^1.3.3 + version: 1.3.3 + body-parser: + specifier: ^1.20.2 + version: 1.20.2 + bs58: + specifier: ^5.0.0 + version: 5.0.0 + express: + specifier: ^4.18.2 + version: 4.18.2 + devDependencies: + '@types/body-parser': + specifier: ^1.19.5 + version: 1.19.5 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/node': + specifier: ^20.10.4 + version: 20.10.4 + '@vitest/coverage-v8': + specifier: ^0.34.2 + version: 0.34.2(vitest@0.34.2) + eslint: + specifier: ^8.47.0 + version: 8.48.0 + eslint-config-explorerkit: + specifier: workspace:* + version: link:../eslint-config-explorerkit + nodemon: + specifier: ^3.0.2 + version: 3.0.2 + vitest: + specifier: ^0.34.2 + version: 0.34.2 + packages/explorerkit-translator: dependencies: '@coral-xyz/anchor': @@ -638,7 +693,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.23.0 ignore: 5.2.4 @@ -664,7 +719,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -877,6 +932,13 @@ packages: dayjs: 1.11.10 dev: false + /@types/body-parser@1.19.5: + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + dependencies: + '@types/connect': 3.4.37 + '@types/node': 20.10.4 + dev: true + /@types/chai-subset@1.3.4: resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} dependencies: @@ -890,7 +952,29 @@ packages: /@types/connect@3.4.37: resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==} dependencies: - '@types/node': 12.20.55 + '@types/node': 20.10.4 + + /@types/express-serve-static-core@4.17.41: + resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} + dependencies: + '@types/node': 20.10.4 + '@types/qs': 6.9.10 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + dev: true + + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.17.41 + '@types/qs': 6.9.10 + '@types/serve-static': 1.15.5 + dev: true + + /@types/http-errors@2.0.4: + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: true /@types/is-ci@3.0.3: resolution: {integrity: sha512-FdHbjLiN2e8fk9QYQyVYZrK8svUDJpxSaSWLUga8EZS1RGAvvrqM9zbVARBtQuYPeLgnJxM2xloOswPwj1o2cQ==} @@ -906,6 +990,14 @@ packages: resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} dev: false + /@types/mime@1.3.5: + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: true + + /@types/mime@3.0.4: + resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} + dev: true + /@types/minimist@1.2.4: resolution: {integrity: sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==} dev: true @@ -913,23 +1005,45 @@ packages: /@types/node@12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - /@types/node@20.8.8: - resolution: {integrity: sha512-YRsdVxq6OaLfmR9Hy816IMp33xOBjfyOgUd77ehqg96CFywxAPbDbXvAsuN2KVg2HOT8Eh6uAfU+l4WffwPVrQ==} + /@types/node@20.10.4: + resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} dependencies: - undici-types: 5.25.3 - dev: true + undici-types: 5.26.5 /@types/normalize-package-data@2.4.3: resolution: {integrity: sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==} dev: true + /@types/qs@6.9.10: + resolution: {integrity: sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==} + dev: true + + /@types/range-parser@1.2.7: + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: true + /@types/semver@7.5.4: resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==} + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.10.4 + dev: true + + /@types/serve-static@1.15.5: + resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + dependencies: + '@types/http-errors': 2.0.4 + '@types/mime': 3.0.4 + '@types/node': 20.10.4 + dev: true + /@types/ws@7.4.7: resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} dependencies: - '@types/node': 12.20.55 + '@types/node': 20.10.4 /@typescript-eslint/eslint-plugin@6.9.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0)(typescript@5.2.2): resolution: {integrity: sha512-lgX7F0azQwRPB7t7WAyeHWVfW1YJ9NIgd9mvGhfQpRY56X6AVf8mwM8Wol+0z4liE7XX3QOt8MN1rUKCfSjRIA==} @@ -948,7 +1062,7 @@ packages: '@typescript-eslint/type-utils': 6.9.0(eslint@8.52.0)(typescript@5.2.2) '@typescript-eslint/utils': 6.9.0(eslint@8.52.0)(typescript@5.2.2) '@typescript-eslint/visitor-keys': 6.9.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.52.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -974,7 +1088,7 @@ packages: '@typescript-eslint/types': 6.9.0 '@typescript-eslint/typescript-estree': 6.9.0(typescript@5.2.2) '@typescript-eslint/visitor-keys': 6.9.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.52.0 typescript: 5.2.2 transitivePeerDependencies: @@ -1001,7 +1115,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.9.0(typescript@5.2.2) '@typescript-eslint/utils': 6.9.0(eslint@8.52.0)(typescript@5.2.2) - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.52.0 ts-api-utils: 1.0.3(typescript@5.2.2) typescript: 5.2.2 @@ -1025,7 +1139,7 @@ packages: dependencies: '@typescript-eslint/types': 6.9.0 '@typescript-eslint/visitor-keys': 6.9.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -1132,6 +1246,18 @@ packages: jsonparse: 1.3.1 through: 2.3.8 + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + /acorn-jsx@5.3.2(acorn@8.10.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1228,6 +1354,10 @@ packages: is-array-buffer: 3.0.2 dev: true + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: false + /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -1325,6 +1455,46 @@ packages: /bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + /body-parser@1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /borsh@0.7.0: resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} dependencies: @@ -1395,6 +1565,11 @@ packages: load-tsconfig: 0.2.5 dev: true + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -1406,7 +1581,6 @@ packages: function-bind: 1.1.2 get-intrinsic: 1.2.2 set-function-length: 1.1.1 - dev: true /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1549,10 +1723,31 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: false + + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + /cross-fetch@3.1.8: resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} dependencies: @@ -1608,7 +1803,18 @@ packages: resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} dev: false - /debug@4.3.4: + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /debug@4.3.4(supports-color@5.5.0): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} peerDependencies: @@ -1618,6 +1824,7 @@ packages: optional: true dependencies: ms: 2.1.2 + supports-color: 5.5.0 /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -1655,7 +1862,6 @@ packages: get-intrinsic: 1.2.2 gopd: 1.0.1 has-property-descriptors: 1.0.1 - dev: true /define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} @@ -1675,6 +1881,16 @@ packages: engines: {node: '>=0.4.0'} dev: false + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + /detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -1713,6 +1929,10 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true @@ -1721,6 +1941,11 @@ packages: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + /enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1847,6 +2072,10 @@ packages: engines: {node: '>=6'} dev: true + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -1917,7 +2146,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -1964,7 +2193,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -2029,6 +2258,11 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + /eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -2047,6 +2281,45 @@ packages: strip-final-newline: 2.0.0 dev: true + /express@4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + /extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} dev: true @@ -2106,6 +2379,21 @@ packages: dependencies: to-regex-range: 5.0.1 + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2171,6 +2459,16 @@ packages: mime-types: 2.1.35 dev: false + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + /fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -2202,7 +2500,6 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true /function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} @@ -2234,7 +2531,6 @@ packages: has-proto: 1.0.1 has-symbols: 1.0.3 hasown: 2.0.0 - dev: true /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} @@ -2322,7 +2618,6 @@ packages: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.2 - dev: true /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2347,7 +2642,6 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -2357,17 +2651,14 @@ packages: resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} dependencies: get-intrinsic: 1.2.2 - dev: true /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} - dev: true /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - dev: true /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} @@ -2381,7 +2672,6 @@ packages: engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - dev: true /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -2391,6 +2681,17 @@ packages: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + /human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} dev: true @@ -2410,11 +2711,14 @@ packages: engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - dev: true /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + /ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + dev: true + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -2453,6 +2757,11 @@ packages: side-channel: 1.0.4 dev: true + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -2641,7 +2950,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -2864,6 +3173,11 @@ packages: engines: {node: '>=8'} dev: true + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + /meow@6.1.1: resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} engines: {node: '>=8'} @@ -2881,6 +3195,10 @@ packages: yargs-parser: 18.1.3 dev: true + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -2889,6 +3207,11 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -2908,6 +3231,12 @@ packages: mime-db: 1.52.0 dev: false + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2958,9 +3287,17 @@ packages: ufo: 1.3.1 dev: true + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: @@ -2978,6 +3315,11 @@ packages: /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + /no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: @@ -3001,6 +3343,30 @@ packages: hasBin: true requiresBuild: true + /nodemon@3.0.2: + resolution: {integrity: sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chokidar: 3.5.3 + debug: 4.3.4(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.5.4 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.0 + undefsafe: 2.0.5 + dev: true + + /nopt@1.0.10: + resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: true + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -3029,7 +3395,6 @@ packages: /object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - dev: true /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -3046,6 +3411,13 @@ packages: object-keys: 1.1.1 dev: true + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -3148,6 +3520,11 @@ packages: lines-and-columns: 1.2.4 dev: true + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3172,6 +3549,10 @@ packages: minipass: 7.0.4 dev: true + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3276,6 +3657,14 @@ packages: react-is: 18.2.0 dev: true + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false @@ -3284,10 +3673,21 @@ packages: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} dev: true + /pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + dev: true + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3296,6 +3696,31 @@ packages: engines: {node: '>=8'} dev: true + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body@2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true @@ -3448,7 +3873,6 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: true /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} @@ -3462,6 +3886,39 @@ packages: dependencies: lru-cache: 6.0.0 + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: false + /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true @@ -3474,7 +3931,6 @@ packages: get-intrinsic: 1.2.2 gopd: 1.0.1 has-property-descriptors: 1.0.1 - dev: true /set-function-name@2.0.1: resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} @@ -3485,6 +3941,10 @@ packages: has-property-descriptors: 1.0.1 dev: true + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + /shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -3513,7 +3973,6 @@ packages: call-bind: 1.0.5 get-intrinsic: 1.2.2 object-inspect: 1.13.1 - dev: true /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3528,6 +3987,13 @@ packages: engines: {node: '>=14'} dev: true + /simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -3606,6 +4072,11 @@ packages: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} dev: true + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + /std-env@3.4.3: resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} dev: true @@ -3725,7 +4196,6 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -3801,10 +4271,22 @@ packages: dependencies: is-number: 7.0.0 + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + /toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} dev: false + /touch@3.1.0: + resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} + hasBin: true + dependencies: + nopt: 1.0.10 + dev: true + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3860,7 +4342,7 @@ packages: bundle-require: 4.0.2(esbuild@0.18.20) cac: 6.7.14 chokidar: 3.5.3 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) esbuild: 0.18.20 execa: 5.1.1 globby: 11.1.0 @@ -3890,64 +4372,64 @@ packages: yargs: 17.7.2 dev: true - /turbo-darwin-64@1.10.16: - resolution: {integrity: sha512-+Jk91FNcp9e9NCLYlvDDlp2HwEDp14F9N42IoW3dmHI5ZkGSXzalbhVcrx3DOox3QfiNUHxzWg4d7CnVNCuuMg==} + /turbo-darwin-64@1.11.2: + resolution: {integrity: sha512-toFmRG/adriZY3hOps7nYCfqHAS+Ci6xqgX3fbo82kkLpC6OBzcXnleSwuPqjHVAaRNhVoB83L5njcE9Qwi2og==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.10.16: - resolution: {integrity: sha512-jqGpFZipIivkRp/i+jnL8npX0VssE6IAVNKtu573LXtssZdV/S+fRGYA16tI46xJGxSAivrZ/IcgZrV6Jk80bw==} + /turbo-darwin-arm64@1.11.2: + resolution: {integrity: sha512-FCsEDZ8BUSFYEOSC3rrARQrj7x2VOrmVcfrMUIhexTxproRh4QyMxLfr6LALk4ymx6jbDCxWa6Szal8ckldFbA==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.10.16: - resolution: {integrity: sha512-PpqEZHwLoizQ6sTUvmImcRmACyRk9EWLXGlqceogPZsJ1jTRK3sfcF9fC2W56zkSIzuLEP07k5kl+ZxJd8JMcg==} + /turbo-linux-64@1.11.2: + resolution: {integrity: sha512-Vzda/o/QyEske5CxLf0wcu7UUS+7zB90GgHZV4tyN+WZtoouTvbwuvZ3V6b5Wgd3OJ/JwWR0CXDK7Sf4VEMr7A==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.10.16: - resolution: {integrity: sha512-TMjFYz8to1QE0fKVXCIvG/4giyfnmqcQIwjdNfJvKjBxn22PpbjeuFuQ5kNXshUTRaTJihFbuuCcb5OYFNx4uw==} + /turbo-linux-arm64@1.11.2: + resolution: {integrity: sha512-bRLwovQRz0yxDZrM4tQEAYV0fBHEaTzUF0JZ8RG1UmZt/CqtpnUrJpYb1VK8hj1z46z9YehARpYCwQ2K0qU4yw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.10.16: - resolution: {integrity: sha512-+jsf68krs0N66FfC4/zZvioUap/Tq3sPFumnMV+EBo8jFdqs4yehd6+MxIwYTjSQLIcpH8KoNMB0gQYhJRLZzw==} + /turbo-windows-64@1.11.2: + resolution: {integrity: sha512-LgTWqkHAKgyVuLYcEPxZVGPInTjjeCnN5KQMdJ4uQZ+xMDROvMFS2rM93iQl4ieDJgidwHCxxCxaU9u8c3d/Kg==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.10.16: - resolution: {integrity: sha512-sKm3hcMM1bl0B3PLG4ifidicOGfoJmOEacM5JtgBkYM48ncMHjkHfFY7HrJHZHUnXM4l05RQTpLFoOl/uIo2HQ==} + /turbo-windows-arm64@1.11.2: + resolution: {integrity: sha512-829aVBU7IX0c/B4G7g1VI8KniAGutHhIupkYMgF6xPkYVev2G3MYe6DMS/vsLt9GGM9ulDtdWxWrH5P2ngK8IQ==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.10.16: - resolution: {integrity: sha512-2CEaK4FIuSZiP83iFa9GqMTQhroW2QryckVqUydmg4tx78baftTOS0O+oDAhvo9r9Nit4xUEtC1RAHoqs6ZEtg==} + /turbo@1.11.2: + resolution: {integrity: sha512-jPC7LVQJzebs5gWf8FmEvsvXGNyKbN+O9qpvv98xpNaM59aS0/Irhd0H0KbcqnXfsz7ETlzOC3R+xFWthC4Z8A==} hasBin: true optionalDependencies: - turbo-darwin-64: 1.10.16 - turbo-darwin-arm64: 1.10.16 - turbo-linux-64: 1.10.16 - turbo-linux-arm64: 1.10.16 - turbo-windows-64: 1.10.16 - turbo-windows-arm64: 1.10.16 + turbo-darwin-64: 1.11.2 + turbo-darwin-arm64: 1.11.2 + turbo-linux-64: 1.11.2 + turbo-linux-arm64: 1.11.2 + turbo-windows-64: 1.11.2 + turbo-windows-arm64: 1.11.2 dev: true /type-check@0.4.0: @@ -3980,6 +4462,14 @@ packages: engines: {node: '>=8'} dev: true + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + /typed-array-buffer@1.0.0: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'} @@ -4037,15 +4527,23 @@ packages: which-boxed-primitive: 1.0.2 dev: true - /undici-types@5.25.3: - resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==} + /undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} dev: true + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -4058,6 +4556,11 @@ packages: dependencies: node-gyp-build: 4.6.1 + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + dev: false + /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -4078,17 +4581,22 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite-node@0.34.2(@types/node@20.8.8): + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + + /vite-node@0.34.2(@types/node@20.10.4): resolution: {integrity: sha512-JtW249Zm3FB+F7pQfH56uWSdlltCo1IOkZW5oHBzeQo0iX4jtC7o1t9aILMGd9kVekXBP2lfJBEQt9rBh07ebA==} engines: {node: '>=v14.18.0'} hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.0(@types/node@20.8.8) + vite: 4.5.0(@types/node@20.10.4) transitivePeerDependencies: - '@types/node' - less @@ -4100,7 +4608,7 @@ packages: - terser dev: true - /vite@4.5.0(@types/node@20.8.8): + /vite@4.5.0(@types/node@20.10.4): resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -4128,7 +4636,7 @@ packages: terser: optional: true dependencies: - '@types/node': 20.8.8 + '@types/node': 20.10.4 esbuild: 0.18.20 postcss: 8.4.31 rollup: 3.29.4 @@ -4169,7 +4677,7 @@ packages: dependencies: '@types/chai': 4.3.9 '@types/chai-subset': 1.3.4 - '@types/node': 20.8.8 + '@types/node': 20.10.4 '@vitest/expect': 0.34.2 '@vitest/runner': 0.34.2 '@vitest/snapshot': 0.34.2 @@ -4179,7 +4687,7 @@ packages: acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.10 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) local-pkg: 0.4.3 magic-string: 0.30.5 pathe: 1.1.1 @@ -4188,8 +4696,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.1 tinypool: 0.7.0 - vite: 4.5.0(@types/node@20.8.8) - vite-node: 0.34.2(@types/node@20.8.8) + vite: 4.5.0(@types/node@20.10.4) + vite-node: 0.34.2(@types/node@20.10.4) why-is-node-running: 2.2.2 transitivePeerDependencies: - less From 50bf0e3801f53e946b268b51c6106210b38c8c75 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Wed, 13 Dec 2023 20:38:09 +0100 Subject: [PATCH 03/11] Fix linter --- packages/explorerkit-server/src/index.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/explorerkit-server/src/index.ts b/packages/explorerkit-server/src/index.ts index 9949351..9c31f29 100644 --- a/packages/explorerkit-server/src/index.ts +++ b/packages/explorerkit-server/src/index.ts @@ -1,9 +1,9 @@ -import express, { Request, Response } from "express"; -import bodyParser from "body-parser"; -import { SolanaFMParser, checkIfAccountParser, checkIfInstructionParser, ParserType } from "@solanafm/explorer-kit"; +import { Message, MessageV0, PublicKey, VersionedTransaction } from "@solana/web3.js"; +import { checkIfAccountParser, checkIfInstructionParser, ParserType, SolanaFMParser } from "@solanafm/explorer-kit"; import { getProgramIdl } from "@solanafm/explorer-kit-idls"; -import { VersionedTransaction, Message, MessageV0, PublicKey } from "@solana/web3.js"; +import bodyParser from "body-parser"; import bs58 from "bs58"; +import express, { Request, Response } from "express"; interface Account { ownerProgram: string; @@ -161,6 +161,4 @@ function isValidBase64(str: string): boolean { // Start the server const PORT = 3000; -app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); -}); +app.listen(PORT); From 663a099d52b9bf99d3d779a35478acbe908b2b49 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Wed, 13 Dec 2023 21:38:39 +0100 Subject: [PATCH 04/11] Add tests --- packages/explorerkit-server/package.json | 2 + packages/explorerkit-server/src/index.ts | 161 +---------------- packages/explorerkit-server/src/server.ts | 166 ++++++++++++++++++ .../explorerkit-server/tests/server.test.ts | 92 ++++++++++ pnpm-lock.yaml | 123 +++++++++++-- 5 files changed, 373 insertions(+), 171 deletions(-) create mode 100644 packages/explorerkit-server/src/server.ts create mode 100644 packages/explorerkit-server/tests/server.test.ts diff --git a/packages/explorerkit-server/package.json b/packages/explorerkit-server/package.json index 0c3b0a3..8e22008 100644 --- a/packages/explorerkit-server/package.json +++ b/packages/explorerkit-server/package.json @@ -32,10 +32,12 @@ "@types/body-parser": "^1.19.5", "@types/express": "^4.17.21", "@types/node": "^20.10.4", + "@types/supertest": "^2.0.16", "@vitest/coverage-v8": "^0.34.2", "eslint": "^8.47.0", "eslint-config-explorerkit": "workspace:*", "nodemon": "^3.0.2", + "supertest": "^6.3.3", "vitest": "^0.34.2" }, "dependencies": { diff --git a/packages/explorerkit-server/src/index.ts b/packages/explorerkit-server/src/index.ts index 9c31f29..d48ded6 100644 --- a/packages/explorerkit-server/src/index.ts +++ b/packages/explorerkit-server/src/index.ts @@ -1,163 +1,4 @@ -import { Message, MessageV0, PublicKey, VersionedTransaction } from "@solana/web3.js"; -import { checkIfAccountParser, checkIfInstructionParser, ParserType, SolanaFMParser } from "@solanafm/explorer-kit"; -import { getProgramIdl } from "@solanafm/explorer-kit-idls"; -import bodyParser from "body-parser"; -import bs58 from "bs58"; -import express, { Request, Response } from "express"; - -interface Account { - ownerProgram: string; - data: string; -} - -interface DecodeAccountsRequestBody { - accounts: Account[]; -} - -interface DecodedAccount { - error: string | null; - decodedData: any | null; -} - -interface DecodeTransactionsRequestBody { - transactions: string[]; -} - -interface DecodedTransactions { - error: string | null; - decodedInstructions: any[] | null; -} - -interface GenericInstruction { - programId: string; - data: Uint8Array; -} - -const app = express(); -app.use(bodyParser.json()); - -// Endpoint to decode accounts data -app.post("/decode/accounts", async (req: Request, res: Response) => { - const { accounts } = req.body as DecodeAccountsRequestBody; - - let decodedAccounts: DecodedAccount[] = []; - for (var account of accounts) { - if (!isValidBase58(account.ownerProgram)) { - decodedAccounts.push({ error: "'account.ownerProgram' is not a valid base58 string.", decodedData: null }); - continue; - } - if (!isValidBase64(account.data)) { - decodedAccounts.push({ error: "'account.data' is not a valid base64 string.", decodedData: null }); - continue; - } - - const SFMIdlItem = await getProgramIdl(account.ownerProgram); - if (SFMIdlItem === null) { - decodedAccounts.push({ error: "Failed to find program IDL", decodedData: null }); - continue; - } - - const parser = new SolanaFMParser(SFMIdlItem, account.ownerProgram); - const eventParser = parser.createParser(ParserType.ACCOUNT); - - if (eventParser && checkIfAccountParser(eventParser)) { - // Parse the transaction - const decodedData = eventParser.parseAccount(account.data); - decodedAccounts.push({ error: null, decodedData }); - continue; - } else { - decodedAccounts.push({ error: "Failed to parse account", decodedData: null }); - continue; - } - } - - return res.status(200).json({ decodedAccounts }); -}); - -// // Endpoint to decode transactions -app.post("/decode/transactions", async (req: Request, res: Response) => { - const { transactions } = req.body as DecodeTransactionsRequestBody; - - let decodedAccounts: DecodedTransactions[] = []; - for (var encodedTx of transactions) { - let txBuffer = null; - if (isValidBase64(encodedTx)) { - txBuffer = Buffer.from(encodedTx, "base64"); - } else if (isValidBase58(encodedTx)) { - txBuffer = Buffer.from(bs58.decode(encodedTx)); - } else { - decodedAccounts.push({ error: "'transaction' is not a valid base64 string.", decodedInstructions: null }); - continue; - } - - try { - const tx = VersionedTransaction.deserialize(txBuffer); - let instructions: GenericInstruction[] = []; - if (tx.message instanceof Message) { - for (var ix of tx.message.instructions) { - instructions.push({ - programId: tx.message.accountKeys[ix.programIdIndex]?.toString() as string, // TODO(fabio): Fix me - data: bs58.decode(ix.data), - }); - } - } else if (tx.message instanceof MessageV0) { - for (var inst of tx.message.compiledInstructions) { - instructions.push({ - programId: (tx.message.staticAccountKeys[inst.programIdIndex] as PublicKey).toString(), // TODO(fabio): Fix me - data: inst.data, - }); - } - } else { - throw new Error("Unsupported message version"); - } - - let decodedInstructions: any[] = []; - for (var instruction of instructions) { - const programId = instruction.programId.toString(); - - const SFMIdlItem = await getProgramIdl(programId); - if (SFMIdlItem) { - const parser = new SolanaFMParser(SFMIdlItem, programId); - const instructionParser = parser.createParser(ParserType.INSTRUCTION); - - if (instructionParser && checkIfInstructionParser(instructionParser)) { - // Parse the transaction - const decodedInstruction = instructionParser.parseInstructions(bs58.encode(instruction.data)); - decodedInstructions.push(decodedInstruction); - } - } else { - decodedAccounts.push({ error: "Failed to find program IDL", decodedInstructions: null }); - } - } - - decodedAccounts.push({ error: null, decodedInstructions }); - } catch (e: any) { - decodedAccounts.push({ error: e.message, decodedInstructions: null }); - } - } - - return res.status(200).json({ decodedAccounts }); -}); - -// Function to validate base58 string -function isValidBase58(str: string): boolean { - const base58Regex = /^[A-HJ-NP-Za-km-z1-9]+$/; - return base58Regex.test(str); -} - -function isValidBase64(str: string): boolean { - try { - // Decode the string from Base64 and then re-encode it - const base64Encoded = Buffer.from(str, "base64").toString("base64"); - - // Check if the re-encoded string matches the original - // This ensures that the input was valid Base64 - return str === base64Encoded; - } catch (e) { - // If an error occurs during decoding, the string was not valid Base64 - return false; - } -} +import { app } from "./server"; // Start the server const PORT = 3000; diff --git a/packages/explorerkit-server/src/server.ts b/packages/explorerkit-server/src/server.ts new file mode 100644 index 0000000..569a151 --- /dev/null +++ b/packages/explorerkit-server/src/server.ts @@ -0,0 +1,166 @@ +import { Message, MessageV0, VersionedTransaction } from "@solana/web3.js"; +import { checkIfAccountParser, checkIfInstructionParser, ParserType, SolanaFMParser } from "@solanafm/explorer-kit"; +import { getProgramIdl } from "@solanafm/explorer-kit-idls"; +import bodyParser from "body-parser"; +import bs58 from "bs58"; +import express, { Express, Request, Response } from "express"; + +interface Account { + ownerProgram: string; + data: string; +} + +interface DecodeAccountsRequestBody { + accounts: Account[]; +} + +interface DecodedAccount { + error: string | null; + decodedData: any | null; +} + +interface DecodeTransactionsRequestBody { + transactions: string[]; +} + +interface DecodedTransactions { + error: string | null; + decodedInstructions: any[] | null; +} + +interface GenericInstruction { + programId: string; + data: Uint8Array; +} + +const app: Express = express(); +app.use(bodyParser.json()); + +// Endpoint to decode accounts data +app.post("/decode/accounts", async (req: Request, res: Response) => { + const { accounts } = req.body as DecodeAccountsRequestBody; + + let decodedAccounts: DecodedAccount[] = []; + for (var account of accounts) { + if (!isValidBase58(account.ownerProgram)) { + decodedAccounts.push({ error: "'account.ownerProgram' is not a valid base58 string.", decodedData: null }); + continue; + } + if (!isValidBase64(account.data)) { + decodedAccounts.push({ error: "'account.data' is not a valid base64 string.", decodedData: null }); + continue; + } + + const SFMIdlItem = await getProgramIdl(account.ownerProgram); + if (SFMIdlItem === null) { + decodedAccounts.push({ error: "Failed to find program IDL", decodedData: null }); + continue; + } + + const parser = new SolanaFMParser(SFMIdlItem, account.ownerProgram); + const eventParser = parser.createParser(ParserType.ACCOUNT); + + if (eventParser && checkIfAccountParser(eventParser)) { + // Parse the transaction + const decodedData = eventParser.parseAccount(account.data); + decodedAccounts.push({ error: null, decodedData }); + continue; + } else { + decodedAccounts.push({ error: "Failed to parse account", decodedData: null }); + continue; + } + } + + return res.status(200).json({ decodedAccounts }); +}); + +// Endpoint to decode transactions +app.post("/decode/transactions", async (req: Request, res: Response) => { + const { transactions } = req.body as DecodeTransactionsRequestBody; + + let decodedAccounts: DecodedTransactions[] = []; + for (var encodedTx of transactions) { + let txBuffer = null; + if (isValidBase64(encodedTx)) { + txBuffer = Buffer.from(encodedTx, "base64"); + } else if (isValidBase58(encodedTx)) { + txBuffer = Buffer.from(bs58.decode(encodedTx)); + } else { + decodedAccounts.push({ error: "'transaction' is not a valid base64 string.", decodedInstructions: null }); + continue; + } + + try { + const tx = VersionedTransaction.deserialize(txBuffer); + let instructions: GenericInstruction[] = []; + if (tx.message instanceof Message) { + for (var ix of tx.message.instructions) { + let programId = tx.message.accountKeys[ix.programIdIndex]; + if (programId === undefined) { + decodedAccounts.push({ error: "programId not found in accounts", decodedInstructions: null }); + continue; + } + instructions.push({ + programId: programId.toString(), // We know programId will exist + data: bs58.decode(ix.data), + }); + } + } else if (tx.message instanceof MessageV0) { + for (var inst of tx.message.compiledInstructions) { + let programId = tx.message.staticAccountKeys[inst.programIdIndex]; + if (programId === undefined) { + decodedAccounts.push({ error: "programId not found in staticAccountKeys", decodedInstructions: null }); + continue; + } + instructions.push({ + programId: programId.toString(), + data: inst.data, + }); + } + } else { + throw new Error("Unsupported message version"); + } + + let decodedInstructions: any[] = []; + for (var instruction of instructions) { + const programId = instruction.programId.toString(); + + const SFMIdlItem = await getProgramIdl(programId); + if (SFMIdlItem) { + const parser = new SolanaFMParser(SFMIdlItem, programId); + const instructionParser = parser.createParser(ParserType.INSTRUCTION); + + if (instructionParser && checkIfInstructionParser(instructionParser)) { + // Parse the transaction + const decodedInstruction = instructionParser.parseInstructions(bs58.encode(instruction.data)); + decodedInstructions.push(decodedInstruction); + } + } else { + decodedAccounts.push({ error: "Failed to find program IDL", decodedInstructions: null }); + } + } + + decodedAccounts.push({ error: null, decodedInstructions }); + } catch (e: any) { + decodedAccounts.push({ error: e.message, decodedInstructions: null }); + } + } + + return res.status(200).json({ decodedAccounts }); +}); + +function isValidBase58(str: string): boolean { + const base58Regex = /^[A-HJ-NP-Za-km-z1-9]+$/; + return base58Regex.test(str); +} + +function isValidBase64(str: string): boolean { + try { + const base64Encoded = Buffer.from(str, "base64").toString("base64"); + return str === base64Encoded; + } catch (e) { + return false; + } +} + +export { app }; diff --git a/packages/explorerkit-server/tests/server.test.ts b/packages/explorerkit-server/tests/server.test.ts new file mode 100644 index 0000000..597dd14 --- /dev/null +++ b/packages/explorerkit-server/tests/server.test.ts @@ -0,0 +1,92 @@ +import request from "supertest"; +import { describe, expect, it } from "vitest"; + +import { app } from "../src/server"; + +describe("Server API Tests", () => { + it("Decodes accounts correctly", async () => { + const res = await request(app) + .post("/decode/accounts") + .send({ + accounts: [ + { + ownerProgram: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + data: "/NFB6YMsrxCtkXSVyg8nG1spPNRwJ+pzcAftQOs5oL0mA+FEPRpnATHIUtp5LuY9RJEScraeiSf6ghxvpIcl2eGPjQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + }, + ], + }); + + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ + decodedAccounts: [ + { + error: null, + decodedData: { + name: "tokenAccount", + data: { + mint: "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", + owner: "3ZPvbCiQuo3HxSiqQejCUTVWUrnpF3EbBCrrbtPJEBkU", + amount: "93163489", + delegate: null, + accountState: { + enumType: "initialized", + }, + isNative: null, + delegatedAmount: "0", + closeAuthority: null, + }, + type: "account", + }, + }, + ], + }); + }); + + it("Decodes transactions correctly", async () => { + const res = await request(app) + .post("/decode/transactions") + .send({ + transactions: [ + "B8WHcXetQ5nZKHNhZFK6NYeyL9whFEczxqSXn8m8Gy7LvjwrNYgKT6Wm2ZuXu76cbZc1Nj2DX8N83h7AsaJ4fHQUFx2nEXqQM22iKT1oBkWSimnRXGT1k2JQBr45kgpC5JFgxYYHkKd2s6f6hfxby4uh2JPTzv3j3vt8BwZEbF6x9jqZUo3385RYCPFz44nbTtDZ8mN34pv2ZvpH7RoAf5QvjofAWzUG97sDa4rtaaemMR6tQsuZRDd3oJ7btm1kLtHRxmZDiL2aHNY5rkRTRbWEVm1tDyjWB5c7KxGBBKNH5u2ztQcAZSp7Dstiyn4cqjZEBVNd3vAQY6n61sutfYPGN5xxgrgxV6EkjpASFKt7PzHRSvpEonLUHHKB955ZHbnbNXSvUp9vv4vD5Xji3FY86TT9SYRRSrs2NJ6dD66NB1MSEoPnhmKRtmM1coh", + ], + }); + + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ + decodedAccounts: [ + { + error: null, + decodedInstructions: [ + { + name: "setComputeUnitPrice", + data: { + discriminator: 3, + computeUnitPrice: 50000, + }, + type: "instruction", + }, + { + name: "setComputeUnitLimit", + data: { + discriminator: 2, + computeUnitLimit: 200000, + }, + type: "instruction", + }, + { + name: "authorize", + data: { + discriminator: 1, + authorizePubkey: "CompuYiTmdxvHERMig9DKw1i8i1VpX4oMzgFoMd2LFrM", + stakeAuthorize: { + enumType: "withdrawer", + }, + }, + type: "instruction", + }, + ], + }, + ], + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b336e9..2937c14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,9 +112,15 @@ importers: '@types/node': specifier: ^20.10.4 version: 20.10.4 + '@types/supertest': + specifier: ^2.0.16 + version: 2.0.16 '@vitest/coverage-v8': specifier: ^0.34.2 version: 0.34.2(vitest@0.34.2) + axios-mock-adapter: + specifier: ^1.22.0 + version: 1.22.0(axios@1.3.3) eslint: specifier: ^8.47.0 version: 8.48.0 @@ -124,6 +130,9 @@ importers: nodemon: specifier: ^3.0.2 version: 3.0.2 + supertest: + specifier: ^6.3.3 + version: 6.3.3 vitest: specifier: ^0.34.2 version: 0.34.2 @@ -954,6 +963,10 @@ packages: dependencies: '@types/node': 20.10.4 + /@types/cookiejar@2.1.5: + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + dev: true + /@types/express-serve-static-core@4.17.41: resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} dependencies: @@ -1040,6 +1053,19 @@ packages: '@types/node': 20.10.4 dev: true + /@types/superagent@4.1.24: + resolution: {integrity: sha512-mEafCgyKiMFin24SDzWN7yAADt4gt6YawFiNMp0QS5ZPboORfyxFt0s3VzJKhTaKg9py/4FUmrHLTNfJKt9Rbw==} + dependencies: + '@types/cookiejar': 2.1.5 + '@types/node': 20.10.4 + dev: true + + /@types/supertest@2.0.16: + resolution: {integrity: sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg==} + dependencies: + '@types/superagent': 4.1.24 + dev: true + /@types/ws@7.4.7: resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} dependencies: @@ -1390,19 +1416,32 @@ packages: engines: {node: '>=0.10.0'} dev: true + /asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: true + /assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: true /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: false /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} dev: true + /axios-mock-adapter@1.22.0(axios@1.3.3): + resolution: {integrity: sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==} + peerDependencies: + axios: '>= 0.17.0' + dependencies: + axios: 1.3.3 + fast-deep-equal: 3.1.3 + is-buffer: 2.0.5 + dev: true + /axios@1.3.3: resolution: {integrity: sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==} dependencies: @@ -1411,7 +1450,6 @@ packages: proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - dev: false /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1710,7 +1748,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: false /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -1720,6 +1757,10 @@ packages: engines: {node: '>= 6'} dev: true + /component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1748,6 +1789,10 @@ packages: engines: {node: '>= 0.6'} dev: false + /cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + dev: true + /cross-fetch@3.1.8: resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} dependencies: @@ -1879,7 +1924,6 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: false /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} @@ -1896,6 +1940,13 @@ packages: engines: {node: '>=8'} dev: true + /dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + dev: true + /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2356,6 +2407,10 @@ packages: /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + /fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + dev: true + /fast-stable-stringify@1.0.0: resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} @@ -2434,7 +2489,6 @@ packages: peerDependenciesMeta: debug: optional: true - dev: false /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -2457,7 +2511,15 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: false + + /formidable@2.1.2: + resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} + dependencies: + dezalgo: 1.0.4 + hexoid: 1.0.0 + once: 1.4.0 + qs: 6.11.0 + dev: true /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} @@ -2673,6 +2735,11 @@ packages: dependencies: function-bind: 1.1.2 + /hexoid@1.0.0: + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} + dev: true + /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true @@ -2795,6 +2862,11 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + dev: true + /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -3210,7 +3282,6 @@ packages: /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - dev: false /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} @@ -3222,14 +3293,12 @@ packages: /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: false /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: false /mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} @@ -3237,6 +3306,12 @@ packages: hasBin: true dev: false + /mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + dev: true + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -3667,7 +3742,6 @@ packages: /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - dev: false /pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} @@ -3686,7 +3760,6 @@ packages: engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 - dev: false /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4184,6 +4257,24 @@ packages: ts-interface-checker: 0.1.13 dev: true + /superagent@8.1.2: + resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} + engines: {node: '>=6.4.0 <13 || >=14'} + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.4(supports-color@5.5.0) + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 2.1.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.11.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true + /superstruct@0.14.2: resolution: {integrity: sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==} @@ -4191,6 +4282,16 @@ packages: resolution: {integrity: sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==} dev: false + /supertest@6.3.3: + resolution: {integrity: sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==} + engines: {node: '>=6.4.0'} + dependencies: + methods: 1.1.2 + superagent: 8.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} From 5423d6cc5be958e808b84d4205d108738e5c05aa Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 14 Dec 2023 10:13:47 +0100 Subject: [PATCH 05/11] Improve return data --- packages/explorerkit-server/src/server.ts | 25 ++++++++++++++++--- .../explorerkit-server/tests/server.test.ts | 8 +++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/explorerkit-server/src/server.ts b/packages/explorerkit-server/src/server.ts index 569a151..7f4f394 100644 --- a/packages/explorerkit-server/src/server.ts +++ b/packages/explorerkit-server/src/server.ts @@ -16,7 +16,13 @@ interface DecodeAccountsRequestBody { interface DecodedAccount { error: string | null; - decodedData: any | null; + decodedData: DecodedAccountData | null; +} + +interface DecodedAccountData { + owner: string; + name: string; + data: any; } interface DecodeTransactionsRequestBody { @@ -25,7 +31,13 @@ interface DecodeTransactionsRequestBody { interface DecodedTransactions { error: string | null; - decodedInstructions: any[] | null; + decodedInstructions: DecodedInstruction[] | null; +} + +interface DecodedInstruction { + programId: string; + name: string; + data: any; } interface GenericInstruction { @@ -63,7 +75,12 @@ app.post("/decode/accounts", async (req: Request, res: Response) => { if (eventParser && checkIfAccountParser(eventParser)) { // Parse the transaction const decodedData = eventParser.parseAccount(account.data); - decodedAccounts.push({ error: null, decodedData }); + decodedAccounts.push({ + error: null, + decodedData: decodedData + ? { owner: account.ownerProgram, name: decodedData?.name, data: decodedData?.data } + : null, + }); continue; } else { decodedAccounts.push({ error: "Failed to parse account", decodedData: null }); @@ -133,7 +150,7 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { if (instructionParser && checkIfInstructionParser(instructionParser)) { // Parse the transaction const decodedInstruction = instructionParser.parseInstructions(bs58.encode(instruction.data)); - decodedInstructions.push(decodedInstruction); + decodedInstructions.push({ name: decodedInstruction?.name, data: decodedInstruction?.data, programId }); } } else { decodedAccounts.push({ error: "Failed to find program IDL", decodedInstructions: null }); diff --git a/packages/explorerkit-server/tests/server.test.ts b/packages/explorerkit-server/tests/server.test.ts index 597dd14..c57ab92 100644 --- a/packages/explorerkit-server/tests/server.test.ts +++ b/packages/explorerkit-server/tests/server.test.ts @@ -22,6 +22,7 @@ describe("Server API Tests", () => { { error: null, decodedData: { + owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", name: "tokenAccount", data: { mint: "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", @@ -35,7 +36,6 @@ describe("Server API Tests", () => { delegatedAmount: "0", closeAuthority: null, }, - type: "account", }, }, ], @@ -63,7 +63,7 @@ describe("Server API Tests", () => { discriminator: 3, computeUnitPrice: 50000, }, - type: "instruction", + programId: "ComputeBudget111111111111111111111111111111", }, { name: "setComputeUnitLimit", @@ -71,7 +71,7 @@ describe("Server API Tests", () => { discriminator: 2, computeUnitLimit: 200000, }, - type: "instruction", + programId: "ComputeBudget111111111111111111111111111111", }, { name: "authorize", @@ -82,7 +82,7 @@ describe("Server API Tests", () => { enumType: "withdrawer", }, }, - type: "instruction", + programId: "Stake11111111111111111111111111111111111111", }, ], }, From 7e7957cc4c6768f45d266e5e9c19fb5be12c3de2 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 14 Dec 2023 20:55:28 +0100 Subject: [PATCH 06/11] docs(changeset): Create Explore Kit Server --- .changeset/rich-parents-fly.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rich-parents-fly.md diff --git a/.changeset/rich-parents-fly.md b/.changeset/rich-parents-fly.md new file mode 100644 index 0000000..53727d2 --- /dev/null +++ b/.changeset/rich-parents-fly.md @@ -0,0 +1,5 @@ +--- +"@solanafm/explorer-kit-server": major +--- + +Create Explore Kit Server From 33287ae981f35b60912875d8cfcd4b7b0c5d33e1 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 14 Dec 2023 21:09:02 +0100 Subject: [PATCH 07/11] Add to README --- packages/explorerkit-server/README.md | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/explorerkit-server/README.md b/packages/explorerkit-server/README.md index 9a81b0e..4984312 100644 --- a/packages/explorerkit-server/README.md +++ b/packages/explorerkit-server/README.md @@ -1 +1,34 @@ # Explorer Kit Server + +Build & watch + +``` +pnpm dev +``` + +Start server + +``` +pnpm serve +``` + +Example requests + +``` +curl --location 'http://localhost:3000/decode/accounts' --header 'Content-Type: application/json' --data '{ + "accounts": [ + { + "ownerProgram": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "data": "/NFB6YMsrxCtkXSVyg8nG1spPNRwJ+pzcAftQOs5oL0mA+FEPRpnATHIUtp5LuY9RJEScraeiSf6ghxvpIcl2eGPjQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + } + ] +}' +``` + +``` +curl --location 'http://localhost:3000/decode/transactions' --header 'Content-Type: application/json' --data '{ + "transactions": ["B8WHcXetQ5nZKHNhZFK6NYeyL9whFEczxqSXn8m8Gy7LvjwrNYgKT6Wm2ZuXu76cbZc1Nj2DX8N83h7AsaJ4fHQUFx2nEXqQM22iKT1oBkWSimnRXGT1k2JQBr45kgpC5JFgxYYHkKd2s6f6hfxby4uh2JPTzv3j3vt8BwZEbF6x9jqZUo3385RYCPFz44nbTtDZ8mN34pv2ZvpH7RoAf5QvjofAWzUG97sDa4rtaaemMR6tQsuZRDd3oJ7btm1kLtHRxmZDiL2aHNY5rkRTRbWEVm1tDyjWB5c7KxGBBKNH5u2ztQcAZSp7Dstiyn4cqjZEBVNd3vAQY6n61sutfYPGN5xxgrgxV6EkjpASFKt7PzHRSvpEonLUHHKB955ZHbnbNXSvUp9vv4vD5Xji3FY86TT9SYRRSrs2NJ6dD66NB1MSEoPnhmKRtmM1coh"] +}' +``` + +For example responses, see the [tests](./tests/server.test.ts). From 1ab2a96bb3655c57a62fe8f5d18279dea4ebf6aa Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 14 Dec 2023 21:12:27 +0100 Subject: [PATCH 08/11] Remove unused deps --- packages/explorerkit-server/package.json | 4 +--- pnpm-lock.yaml | 27 +++--------------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/packages/explorerkit-server/package.json b/packages/explorerkit-server/package.json index 8e22008..ac091d2 100644 --- a/packages/explorerkit-server/package.json +++ b/packages/explorerkit-server/package.json @@ -26,7 +26,7 @@ "publish-package": "pnpm build && npm publish --access=public" }, "keywords": [], - "author": "SolanaFM", + "author": "fabioberger", "license": "GPL-3.0-or-later", "devDependencies": { "@types/body-parser": "^1.19.5", @@ -41,11 +41,9 @@ "vitest": "^0.34.2" }, "dependencies": { - "@coral-xyz/anchor": "^0.27.0", "@solana/web3.js": "^1.87.2", "@solanafm/explorer-kit": "workspace:*", "@solanafm/explorer-kit-idls": "workspace:*", - "@solanafm/kinobi-lite": "^0.12.0", "axios": "^1.3.3", "body-parser": "^1.20.2", "bs58": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2937c14..09db67f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,9 +75,6 @@ importers: packages/explorerkit-server: dependencies: - '@coral-xyz/anchor': - specifier: ^0.27.0 - version: 0.27.0 '@solana/web3.js': specifier: ^1.87.2 version: 1.87.2 @@ -87,9 +84,6 @@ importers: '@solanafm/explorer-kit-idls': specifier: workspace:* version: link:../explorerkit-idls - '@solanafm/kinobi-lite': - specifier: ^0.12.0 - version: 0.12.0 axios: specifier: ^1.3.3 version: 1.3.3 @@ -118,9 +112,6 @@ importers: '@vitest/coverage-v8': specifier: ^0.34.2 version: 0.34.2(vitest@0.34.2) - axios-mock-adapter: - specifier: ^1.22.0 - version: 1.22.0(axios@1.3.3) eslint: specifier: ^8.47.0 version: 8.48.0 @@ -1432,16 +1423,6 @@ packages: engines: {node: '>= 0.4'} dev: true - /axios-mock-adapter@1.22.0(axios@1.3.3): - resolution: {integrity: sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==} - peerDependencies: - axios: '>= 0.17.0' - dependencies: - axios: 1.3.3 - fast-deep-equal: 3.1.3 - is-buffer: 2.0.5 - dev: true - /axios@1.3.3: resolution: {integrity: sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==} dependencies: @@ -1450,6 +1431,7 @@ packages: proxy-from-env: 1.1.0 transitivePeerDependencies: - debug + dev: false /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2489,6 +2471,7 @@ packages: peerDependenciesMeta: debug: optional: true + dev: false /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -2862,11 +2845,6 @@ packages: has-tostringtag: 1.0.0 dev: true - /is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - dev: true - /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -3742,6 +3720,7 @@ packages: /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false /pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} From 2454b7ea4740d2dc963876b996525c67d78811b0 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Fri, 15 Dec 2023 12:11:55 +0100 Subject: [PATCH 09/11] Since instantiating parsers are expensive, we re-use instantiated parsers when possible --- packages/explorerkit-server/src/server.ts | 78 +++++++++++++---------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/packages/explorerkit-server/src/server.ts b/packages/explorerkit-server/src/server.ts index 7f4f394..21f09ea 100644 --- a/packages/explorerkit-server/src/server.ts +++ b/packages/explorerkit-server/src/server.ts @@ -1,5 +1,11 @@ import { Message, MessageV0, VersionedTransaction } from "@solana/web3.js"; -import { checkIfAccountParser, checkIfInstructionParser, ParserType, SolanaFMParser } from "@solanafm/explorer-kit"; +import { + checkIfAccountParser, + ParserType, + InstructionParserInterface, + AccountParserInterface, + SolanaFMParser, +} from "@solanafm/explorer-kit"; import { getProgramIdl } from "@solanafm/explorer-kit-idls"; import bodyParser from "body-parser"; import bs58 from "bs58"; @@ -52,6 +58,7 @@ app.use(bodyParser.json()); app.post("/decode/accounts", async (req: Request, res: Response) => { const { accounts } = req.body as DecodeAccountsRequestBody; + let accountParsers: { [key: string]: AccountParserInterface } = {}; let decodedAccounts: DecodedAccount[] = []; for (var account of accounts) { if (!isValidBase58(account.ownerProgram)) { @@ -63,29 +70,28 @@ app.post("/decode/accounts", async (req: Request, res: Response) => { continue; } - const SFMIdlItem = await getProgramIdl(account.ownerProgram); - if (SFMIdlItem === null) { - decodedAccounts.push({ error: "Failed to find program IDL", decodedData: null }); - continue; - } + let accountParser = accountParsers[account.ownerProgram]; + if (accountParser == undefined) { + const SFMIdlItem = await getProgramIdl(account.ownerProgram); + if (SFMIdlItem === null) { + decodedAccounts.push({ error: "Failed to find program IDL", decodedData: null }); + continue; + } - const parser = new SolanaFMParser(SFMIdlItem, account.ownerProgram); - const eventParser = parser.createParser(ParserType.ACCOUNT); - - if (eventParser && checkIfAccountParser(eventParser)) { - // Parse the transaction - const decodedData = eventParser.parseAccount(account.data); - decodedAccounts.push({ - error: null, - decodedData: decodedData - ? { owner: account.ownerProgram, name: decodedData?.name, data: decodedData?.data } - : null, - }); - continue; - } else { - decodedAccounts.push({ error: "Failed to parse account", decodedData: null }); - continue; + const parser = new SolanaFMParser(SFMIdlItem, account.ownerProgram); + accountParser = parser.createParser(ParserType.ACCOUNT) as AccountParserInterface; + accountParsers[account.ownerProgram] = accountParser; } + + // Parse the transaction + const decodedData = accountParser.parseAccount(account.data); + decodedAccounts.push({ + error: null, + decodedData: decodedData + ? { owner: account.ownerProgram, name: decodedData?.name, data: decodedData?.data } + : null, + }); + continue; } return res.status(200).json({ decodedAccounts }); @@ -95,6 +101,7 @@ app.post("/decode/accounts", async (req: Request, res: Response) => { app.post("/decode/transactions", async (req: Request, res: Response) => { const { transactions } = req.body as DecodeTransactionsRequestBody; + let instructionParsers: { [key: string]: InstructionParserInterface } = {}; let decodedAccounts: DecodedTransactions[] = []; for (var encodedTx of transactions) { let txBuffer = null; @@ -141,20 +148,23 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { let decodedInstructions: any[] = []; for (var instruction of instructions) { const programId = instruction.programId.toString(); - - const SFMIdlItem = await getProgramIdl(programId); - if (SFMIdlItem) { - const parser = new SolanaFMParser(SFMIdlItem, programId); - const instructionParser = parser.createParser(ParserType.INSTRUCTION); - - if (instructionParser && checkIfInstructionParser(instructionParser)) { - // Parse the transaction - const decodedInstruction = instructionParser.parseInstructions(bs58.encode(instruction.data)); - decodedInstructions.push({ name: decodedInstruction?.name, data: decodedInstruction?.data, programId }); + let instructionParser = instructionParsers[programId]; + + if (instructionParser == undefined) { + const SFMIdlItem = await getProgramIdl(programId); + if (SFMIdlItem) { + const parser = new SolanaFMParser(SFMIdlItem, programId); + instructionParser = parser.createParser(ParserType.INSTRUCTION) as InstructionParserInterface; + instructionParsers[programId] = instructionParser; + } else { + decodedAccounts.push({ error: "Failed to find program IDL", decodedInstructions: null }); + continue; } - } else { - decodedAccounts.push({ error: "Failed to find program IDL", decodedInstructions: null }); } + + // Parse the transaction + const decodedInstruction = instructionParser.parseInstructions(bs58.encode(instruction.data)); + decodedInstructions.push({ name: decodedInstruction?.name, data: decodedInstruction?.data, programId }); } decodedAccounts.push({ error: null, decodedInstructions }); From 38d407f617273d43b1395ccbc03ad769ba9cc544 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Fri, 15 Dec 2023 13:10:32 +0100 Subject: [PATCH 10/11] Since base58 is a subset of base64, we need to check if it's base58 encoded first --- packages/explorerkit-server/src/server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/explorerkit-server/src/server.ts b/packages/explorerkit-server/src/server.ts index 21f09ea..9dbbf00 100644 --- a/packages/explorerkit-server/src/server.ts +++ b/packages/explorerkit-server/src/server.ts @@ -105,10 +105,10 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { let decodedAccounts: DecodedTransactions[] = []; for (var encodedTx of transactions) { let txBuffer = null; - if (isValidBase64(encodedTx)) { - txBuffer = Buffer.from(encodedTx, "base64"); - } else if (isValidBase58(encodedTx)) { + if (isValidBase58(encodedTx)) { txBuffer = Buffer.from(bs58.decode(encodedTx)); + } else if (isValidBase64(encodedTx)) { + txBuffer = Buffer.from(encodedTx, "base64"); } else { decodedAccounts.push({ error: "'transaction' is not a valid base64 string.", decodedInstructions: null }); continue; From 176868250da8a80cfee378a9b4247d68a31b07b4 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Fri, 15 Dec 2023 17:13:31 +0100 Subject: [PATCH 11/11] Rename top-level property to transactions --- packages/explorerkit-server/src/server.ts | 24 +++++++------------ .../explorerkit-server/tests/server.test.ts | 2 +- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/explorerkit-server/src/server.ts b/packages/explorerkit-server/src/server.ts index 9dbbf00..6abe085 100644 --- a/packages/explorerkit-server/src/server.ts +++ b/packages/explorerkit-server/src/server.ts @@ -1,11 +1,5 @@ import { Message, MessageV0, VersionedTransaction } from "@solana/web3.js"; -import { - checkIfAccountParser, - ParserType, - InstructionParserInterface, - AccountParserInterface, - SolanaFMParser, -} from "@solanafm/explorer-kit"; +import { ParserType, InstructionParserInterface, AccountParserInterface, SolanaFMParser } from "@solanafm/explorer-kit"; import { getProgramIdl } from "@solanafm/explorer-kit-idls"; import bodyParser from "body-parser"; import bs58 from "bs58"; @@ -102,7 +96,7 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { const { transactions } = req.body as DecodeTransactionsRequestBody; let instructionParsers: { [key: string]: InstructionParserInterface } = {}; - let decodedAccounts: DecodedTransactions[] = []; + let decodedTransactions: DecodedTransactions[] = []; for (var encodedTx of transactions) { let txBuffer = null; if (isValidBase58(encodedTx)) { @@ -110,7 +104,7 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { } else if (isValidBase64(encodedTx)) { txBuffer = Buffer.from(encodedTx, "base64"); } else { - decodedAccounts.push({ error: "'transaction' is not a valid base64 string.", decodedInstructions: null }); + decodedTransactions.push({ error: "'transaction' is not a valid base64 string.", decodedInstructions: null }); continue; } @@ -121,7 +115,7 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { for (var ix of tx.message.instructions) { let programId = tx.message.accountKeys[ix.programIdIndex]; if (programId === undefined) { - decodedAccounts.push({ error: "programId not found in accounts", decodedInstructions: null }); + decodedTransactions.push({ error: "programId not found in accounts", decodedInstructions: null }); continue; } instructions.push({ @@ -133,7 +127,7 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { for (var inst of tx.message.compiledInstructions) { let programId = tx.message.staticAccountKeys[inst.programIdIndex]; if (programId === undefined) { - decodedAccounts.push({ error: "programId not found in staticAccountKeys", decodedInstructions: null }); + decodedTransactions.push({ error: "programId not found in staticAccountKeys", decodedInstructions: null }); continue; } instructions.push({ @@ -157,7 +151,7 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { instructionParser = parser.createParser(ParserType.INSTRUCTION) as InstructionParserInterface; instructionParsers[programId] = instructionParser; } else { - decodedAccounts.push({ error: "Failed to find program IDL", decodedInstructions: null }); + decodedTransactions.push({ error: "Failed to find program IDL", decodedInstructions: null }); continue; } } @@ -167,13 +161,13 @@ app.post("/decode/transactions", async (req: Request, res: Response) => { decodedInstructions.push({ name: decodedInstruction?.name, data: decodedInstruction?.data, programId }); } - decodedAccounts.push({ error: null, decodedInstructions }); + decodedTransactions.push({ error: null, decodedInstructions }); } catch (e: any) { - decodedAccounts.push({ error: e.message, decodedInstructions: null }); + decodedTransactions.push({ error: e.message, decodedInstructions: null }); } } - return res.status(200).json({ decodedAccounts }); + return res.status(200).json({ decodedTransactions }); }); function isValidBase58(str: string): boolean { diff --git a/packages/explorerkit-server/tests/server.test.ts b/packages/explorerkit-server/tests/server.test.ts index c57ab92..2792f97 100644 --- a/packages/explorerkit-server/tests/server.test.ts +++ b/packages/explorerkit-server/tests/server.test.ts @@ -53,7 +53,7 @@ describe("Server API Tests", () => { expect(res.status).toBe(200); expect(res.body).toMatchObject({ - decodedAccounts: [ + decodedTransactions: [ { error: null, decodedInstructions: [