commit 24d4bcc3e995dbe90ba5d2267c1ad0cf8f9e1473 Author: Chris Troutner Date: Tue Jan 20 08:34:04 2026 -0700 Forked from psf-bch-api diff --git a/.env-example b/.env-example new file mode 100644 index 0000000..59b22be --- /dev/null +++ b/.env-example @@ -0,0 +1,35 @@ +# START INFRASTRUCTURE SETUP + +# Full Node Connection +RPC_BASEURL=http://172.17.0.1:8332 +RPC_USERNAME=bitcoin +RPC_PASSWORD=password + +# Fulcrum Indexer +FULCRUM_API=http://172.17.0.1:3001/v1 + +# SLP Indexer +SLP_INDEXER_API=http://localhost:5010 + +# REST API URL for wallet operations +LOCAL_RESTURL=http://localhost:5942/v6 + +# END INFRASTRUCTURE SETUP + + +# START ACCESS CONTROL + +PORT=5942 + +# x402 payments required to access this API? +X402_ENABLED=true +SERVER_BCH_ADDRESS=bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d +FACILITATOR_URL=http://localhost:4345/facilitator +X402_PRICE_SAT=200 + +# Basic Authentication required to access this API? +USE_BASIC_AUTH=true +BASIC_AUTH_TOKEN=some-random-token + +# END ACCESS CONTROL + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0e02d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.env +logs/ +docs/ +coverage/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..1f36cdf --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright (c) 2025 Christopher Troutner + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2890158 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# psf-bch-api + +This is a REST API for communicating with Bitcoin Cash infrastructure. It replaces [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api), and it implements [x402-bch protocol](https://github.com/x402-bch/x402-bch) to handle payments to access the API. + +## License + +[MIT](./LICENSE.md) + +## x402-bch Payments + +All REST endpoints exposed under the `/v6` prefix are protected by the [`x402-bch-express`](https://www.npmjs.com/package/x402-bch-express) middleware. Each API call requires a BCH payment authorization for **200 satoshis**. The middleware advertises payment requirements via HTTP 402 responses and validates incoming `X-PAYMENT` headers with a configured Facilitator. + +### Configuration + +Environment variables control the payment flow: + +- `X402_ENABLED` — set to `false` (case-insensitive) to disable the middleware. Defaults to enabled. +- `SERVER_BCH_ADDRESS` — BCH cash address that receives funding transactions. Defaults to `bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d`. +- `FACILITATOR_URL` — Root URL of the facilitator service (e.g., `http://localhost:4345/facilitator`). +- `X402_PRICE_SAT` — Optional; override the satoshi price per call (defaults to `200`). + +When `X402_ENABLED=false`, the server continues to operate without payment headers for local development or trusted deployments. + +### Manual Verification + +1. Start or point to an `x402-bch` facilitator service (the example facilitator listens at `http://localhost:4345/facilitator`). +2. Run the API server with the default configuration: `npm start`. +3. Call a protected endpoint without an `X-PAYMENT` header, e.g. `curl -i http://localhost:5942/v6/full-node/control/getNetworkInfo`. The server will respond with HTTP `402` and include payment requirements. +4. Restart the server with `X402_ENABLED=false npm start` to confirm that the same request now bypasses the middleware (useful for local development without payments). + diff --git a/apidoc.json b/apidoc.json new file mode 100644 index 0000000..b7c7631 --- /dev/null +++ b/apidoc.json @@ -0,0 +1,9 @@ +{ + "name": "psf-bch-api REST API", + "version": "1.0.0", + "description": "REST API proxy to Bitcoin Cash infrastructure", + "title": "psf-bch-api REST API", + "url": "http://localhost:5942", + "sampleUrl": "http://localhost:5942" +} + diff --git a/bin/server.js b/bin/server.js new file mode 100644 index 0000000..aad10ef --- /dev/null +++ b/bin/server.js @@ -0,0 +1,287 @@ +/* + Express server for psf-bch-api REST API. + The architecture of the code follows the Clean Architecture pattern. +*/ + +// npm libraries +import express from 'express' +import cors from 'cors' +import dotenv from 'dotenv' +import { paymentMiddleware as x402PaymentMiddleware } from 'x402-bch-express' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' + +// Local libraries +import config from '../src/config/index.js' +import Controllers from '../src/controllers/index.js' +import wlogger from '../src/adapters/wlogger.js' +import { buildX402Routes, getX402Settings, getBasicAuthSettings } from '../src/config/x402.js' +import { basicAuthMiddleware } from '../src/middleware/basic-auth.js' + +// Load environment variables +dotenv.config() + +// Set up global error handlers to prevent server crashes +// These must be set up before the server starts to catch any unhandled errors +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason) + wlogger.error('Unhandled Rejection:', { + promise: promise.toString(), + reason: reason instanceof Error ? reason.stack : String(reason) + }) + // Don't exit the process - log and continue + // The server should remain running to handle other requests +}) + +process.on('uncaughtException', (error) => { + console.error('Uncaught Exception:', error) + wlogger.error('Uncaught Exception:', { + message: error.message, + stack: error.stack + }) + // For uncaught exceptions, we should exit gracefully + // but give time for the process manager to restart + console.log('Exiting after 5 seconds due to uncaught exception. Process manager should restart.') + setTimeout(() => { + process.exit(1) + }, 5000) +}) + +class Server { + constructor () { + // Encapsulate dependencies + this.controllers = new Controllers() + this.config = config + this.process = process + } + + async startServer () { + try { + // Create an Express instance. + const app = express() + + const x402Settings = getX402Settings() + const basicAuthSettings = getBasicAuthSettings() + + // MIDDLEWARE START + app.use(express.json()) + app.use(express.urlencoded({ extended: true })) + + app.use(cors({ + origin: true, // Allow all origins (more reliable than '*') + credentials: false, // Set to true if you need to support credentials + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] + })) + + // URL normalization middleware - collapse multiple slashes + app.use((req, res, next) => { + if (req.url && req.url.includes('//')) { + // Split URL into path and query string + const [path, queryString] = req.url.split('?') + // Collapse multiple consecutive slashes into a single slash + const normalizedPath = path.replace(/\/+/g, '/') + // Reconstruct req.url with normalized path (req.path is read-only and will auto-update) + req.url = queryString ? `${normalizedPath}?${queryString}` : normalizedPath + } + next() + }) + + // Apply basic auth middleware if enabled + // This must run before x402 middleware to set req.locals.basicAuthValid + if (basicAuthSettings.enabled) { + wlogger.info('Basic auth middleware enabled') + app.use(basicAuthMiddleware) + } + + // Apply x402 middleware based on configuration + // Logic: + // - If X402_ENABLED=true AND USE_BASIC_AUTH=true: Apply x402 conditionally (bypass if basic auth valid) + // - If X402_ENABLED=true AND USE_BASIC_AUTH=false: Apply x402 unconditionally (no basic auth bypass) + // - If X402_ENABLED=false AND USE_BASIC_AUTH=true: Require basic auth only + // - If X402_ENABLED=false AND USE_BASIC_AUTH=false: No access control + + // Apply access control middleware based on configuration + if (x402Settings.enabled && basicAuthSettings.enabled) { + // X402_ENABLED=true AND USE_BASIC_AUTH=true: Apply x402 conditionally + const routes = buildX402Routes(this.config.apiPrefix) + const facilitatorOptions = x402Settings.facilitatorUrl + ? { url: x402Settings.facilitatorUrl } + : undefined + + wlogger.info(`x402 middleware enabled with basic auth bypass; enforcing ${x402Settings.priceSat} satoshis per request (unless basic auth provided)`) + + // Create conditional x402 middleware that bypasses if basic auth is valid + const conditionalX402Middleware = (req, res, next) => { + // If basic auth is valid, bypass x402 + if (req.locals?.basicAuthValid === true) { + return next() + } + + // Otherwise, apply x402 middleware + return x402PaymentMiddleware( + x402Settings.serverAddress, + routes, + facilitatorOptions + )(req, res, next) + } + + app.use(conditionalX402Middleware) + } else if (x402Settings.enabled && !basicAuthSettings.enabled) { + // X402_ENABLED=true AND USE_BASIC_AUTH=false: Apply x402 unconditionally (no basic auth bypass) + const routes = buildX402Routes(this.config.apiPrefix) + const facilitatorOptions = x402Settings.facilitatorUrl + ? { url: x402Settings.facilitatorUrl } + : undefined + + wlogger.info(`x402 middleware enabled (basic auth disabled); enforcing ${x402Settings.priceSat} satoshis per request`) + + // Apply x402 middleware unconditionally - no basic auth bypass + app.use(x402PaymentMiddleware( + x402Settings.serverAddress, + routes, + facilitatorOptions + )) + } else if (basicAuthSettings.enabled && !x402Settings.enabled) { + // USE_BASIC_AUTH=true AND X402_ENABLED=false: Require basic auth, reject unauthenticated requests + wlogger.info('Basic auth enforcement enabled (x402 disabled)') + + // Middleware that rejects requests without valid basic auth + const requireBasicAuthMiddleware = (req, res, next) => { + // Skip auth check for health endpoint and root + if (req.path === '/health' || req.path === '/') { + return next() + } + + // If basic auth is valid, allow the request + if (req.locals?.basicAuthValid === true) { + return next() + } + + // Reject unauthenticated requests + wlogger.warn(`Unauthenticated request rejected: ${req.method} ${req.path}`) + return res.status(401).json({ + error: 'Unauthorized', + message: 'Valid Bearer token required in Authorization header' + }) + } + + app.use(requireBasicAuthMiddleware) + } else { + // X402_ENABLED=false AND USE_BASIC_AUTH=false: No access control middleware + wlogger.info('No access control middleware enabled') + } + + // Endpoint logging middleware + app.use((req, res, next) => { + console.log(`Endpoint called: ${req.method} ${req.path} by ${req.ip}`) + res.on('finish', () => { + console.log(`Endpoint responded: ${req.method} ${req.path} - ${res.statusCode}`) + }) + next() + }) + + // Request logging middleware + app.use((req, res, next) => { + wlogger.info(`${req.method} ${req.path}`) + next() + }) + + // Error handling middleware + app.use((err, req, res, next) => { + wlogger.error('Express error:', err) + + // Handle JSON parsing errors + if (err instanceof SyntaxError && err.status === 400 && 'body' in err) { + return res.status(400).json({ + error: 'Invalid JSON in request body' + }) + } + + // Default to 500 for other errors + res.status(500).json({ + error: err.message || 'Internal server error' + }) + }) + + // Wait for any adapters to initialize. + await this.controllers.initAdapters() + + // Wait for any use-libraries to initialize. + await this.controllers.initUseCases() + + // Attach REST API controllers to the app. + this.controllers.attachRESTControllers(app) + + // Initialize any other controller libraries. + this.controllers.initControllers() + + // Serve static assets from docs directory + const __filename = fileURLToPath(import.meta.url) + const __dirname = dirname(__filename) + app.use('/assets', express.static(join(__dirname, '..', 'docs', 'assets'))) + + // Health check endpoint + app.get('/health', (req, res) => { + res.json({ + status: 'ok', + service: 'rest2nostr', + version: config.version + }) + }) + + // Root endpoint + app.get('/', (req, res) => { + const docsPath = join(__dirname, '..', 'docs', 'index.html') + res.sendFile(docsPath) + }) + + // MIDDLEWARE END + + console.log(`Running server in environment: ${this.config.env}`) + wlogger.info(`Running server in environment: ${this.config.env}`) + + this.server = app.listen(this.config.port, () => { + console.log(`Server started on port ${this.config.port}`) + wlogger.info(`Server started on port ${this.config.port}`) + }) + + this.server.on('error', (err) => { + console.error('Server error:', err) + wlogger.error('Server error:', err) + }) + + this.server.on('close', () => { + console.log('Server closed') + wlogger.info('Server closed') + }) + + return app + } catch (err) { + console.error('Could not start server. Error: ', err) + wlogger.error('Could not start server. Error: ', err) + + console.log( + 'Exiting after 5 seconds. Depending on process manager to restart.' + ) + await this.sleep(5000) + this.process.exit(1) + } + } + + sleep (ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) + } +} + +// Start the server if this file is run directly +const __filename = fileURLToPath(import.meta.url) +if (process.argv[1] === __filename) { + const server = new Server() + server.startServer().catch(err => { + console.error('Failed to start server:', err) + process.exit(1) + }) +} + +export default Server diff --git a/dev-docs/README.md b/dev-docs/README.md new file mode 100644 index 0000000..8d6469b --- /dev/null +++ b/dev-docs/README.md @@ -0,0 +1,4 @@ +# Developer Documentation + +- [prompt.md](./prompt.md) - This is the original prompt [trout](https://github.com/christroutner) gave the AI to build the app. He uses Cursor 2.0 in Planning mode and the Composer 1 LLM. +- [plan](./rest2nostr-poxy-api.plan.md) - This is the plan created by the Cursor AI from the prompt and support files it was given. This is the plan it used to build the app. \ No newline at end of file diff --git a/dev-docs/creation-prompt.md b/dev-docs/creation-prompt.md new file mode 100644 index 0000000..7be8eb3 --- /dev/null +++ b/dev-docs/creation-prompt.md @@ -0,0 +1,34 @@ +## Build a REST API implementing REST2NOSTR Proxy + +Your task is to plan out the building of a REST API server that implements the idea for a REST2NOSTR proxy. The code for this REST API server app should be placed in the `/app` directory. You can edit any files in the `/app` directory. The REST API server should be built using node.js JavaScript and the express.js library. It should also use dotenv to manage the use of environment variables. It should use [api-doc](https://www.npmjs.com/package/api-doc) to generate API documentation for the REST API. + +### Nostr + +Nostr is a social media protocol. It is defined by nip-01.md in the `nostr/` directory. This is the primary specification that defines Nostr, but there are many other NIPS for other features. If you need to dig deeper into the Nostr standards you can explore the [NIPs on Github](https://github.com/nostr-protocol/nips/tree/master). + +The `nostr-sandbox/` directory contains a series of small example code snippets for common social media use-cases using Nostr. You can study this code understand how a Nostr Client would interact with a Nostr Relay. + +[This discussion thread on GitHub](https://github.com/nostr-protocol/nips/issues/1549) introduces a concept called REST2NOSTR. It solves an common issue experienced by JavaScript web developers: a high preference for REST APIs over Websockets. The proposal is to create a REST API server that operates as a proxy for the Websockets protocol used by Nostr Relays. This would allow Client developers to interact with a Nostr Relay over a familiar REST API, instead of using Websockets. Here is a summary of the API endpoints the discussion proposes: + +| REST Method | Endpoint | Nostr WebSocket Equivalent | Purpose | +| --- | --- | --- | --- | +| `POST` | `/event` | `["EVENT", ]` | Publish a signed Nostr event to the relay. | +| `GET` | `/req/` | `["REQ", , ]` | Retrieve a list of events based on filters (stateless query). | +| `POST`/`PUT` | `/req/` | `["REQ", , ]` | Establish a subscription (for long-polling or SSE). | +| `DELETE` | `/req/` | `["CLOSE", ]` | Close an existing subscription. | + +Your task is to plan out the building of a REST API server that implements the idea for a REST2NOSTR proxy. All code examples in the `nostr-sandbox/` directory that interact with a Relay over Websockets should be able to be implemented using the new REST API. The code examples are a good benchmark to use as a frame of reference as to weather the REST API has been implemented correctly. It would be a good idea to create an `examples/` directory that contain many of these code example, refactored for use with the new REST API. + +There is a similar implementation of REST2NOSTR available at [https://nostr-api.com/](https://nostr-api.com/). While the API and documentation are available, the source code for that implementation is not available. However, it is good to study as an example of the kind of output we are looking for. + +### Follow the Clean Architecture code pattern + +As you plan out the code layout, you should follow the Clean Architecture patterns. Here is background information you can follow to ensure you follow the Clean Architecture pattern: + +* [Clean Architecture Summary](https://raw.githubusercontent.com/christroutner/trouts-blog/refs/heads/master/blog/2021-07-06-clean-architecture/index.md) - This is a markdown document that summarizes the Clean Architecture pattern, and links to additional support information. +* [ipfs-service-provider](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider) - This is a node.js JavaScript code base that follows the Clean Architecture patterns. Notice that within the `src` directory, the sub-directories are split up according to the guidance in the Clean Architecture Summary article. This is the primary pattern you should follow. + * A copy of the ipfs-service-provider repository has been copied to the `clean-architecture/` directory, so that you can study the code locally. + +### Summary + +Your task is to plan out the building of a REST API server that implements the idea for a REST2NOSTR proxy. The code for this REST API server app should be placed in the `/app` directory. You can edit any files in the `/app` directory. The REST API server should be built using node.js JavaScript and the express.js library. It should also use dotenv to manage the use of environment variables. It should use [api-doc](https://www.npmjs.com/package/api-doc) to generate API documentation for the REST API. \ No newline at end of file diff --git a/dev-docs/rest2nostr-poxy-api.plan.md b/dev-docs/rest2nostr-poxy-api.plan.md new file mode 100644 index 0000000..094eebe --- /dev/null +++ b/dev-docs/rest2nostr-poxy-api.plan.md @@ -0,0 +1,163 @@ +# REST2NOSTR Proxy API Implementation Plan + +## Overview + +Build a REST API server in `/app` that proxies Nostr WebSocket protocol to REST endpoints, enabling JavaScript developers to interact with Nostr relays via familiar REST APIs instead of WebSockets. + +## Architecture + +Follow Clean Architecture pattern with these layers: + +- **Entities** (`src/entities/`): Domain models (Event, Subscription, etc.) +- **Use Cases** (`src/use-cases/`): Business logic (PublishEvent, QueryEvents, ManageSubscription) +- **Adapters** (`src/adapters/`): External interfaces (NostrRelay WebSocket client, logger) +- **Controllers** (`src/controllers/rest-api/`): Express.js route handlers + +## Directory Structure + +``` +/app +├── src/ +│ ├── entities/ +│ │ └── event.js +│ ├── use-cases/ +│ │ ├── index.js +│ │ ├── publish-event.js +│ │ ├── query-events.js +│ │ └── manage-subscription.js +│ ├── adapters/ +│ │ ├── index.js +│ │ ├── nostr-relay.js +│ │ └── wlogger.js +│ ├── controllers/ +│ │ ├── index.js +│ │ └── rest-api/ +│ │ ├── index.js +│ │ ├── event/ +│ │ │ ├── controller.js +│ │ │ └── index.js +│ │ └── req/ +│ │ ├── controller.js +│ │ └── index.js +│ └── config/ +│ ├── index.js +│ └── env/ +│ ├── common.js +│ ├── development.js +│ └── production.js +├── examples/ +│ └── [refactored sandbox examples] +├── bin/ +│ └── server.js +├── .env.example +├── .env +├── index.js +├── package.json +└── apidoc.json +``` + +## Core Endpoints + +### POST /event + +- Maps to: `["EVENT", ]` +- Publish a signed Nostr event to relay +- Body: JSON event object +- Response: `{"accepted": true/false, "message": ""}` (maps to `["OK", ...]`) + +### GET /req/:subId + +- Maps to: `["REQ", , ]` +- Stateless query - returns events immediately +- Query params: filters (JSON encoded or separate params) +- Response: Array of events + +### POST /req/:subId + +- Maps to: `["REQ", , ]` +- Establish subscription for Server-Sent Events (SSE) +- Body: filters object +- Response: SSE stream of events + +### DELETE /req/:subId + +- Maps to: `["CLOSE", ]` +- Close an existing subscription +- Response: Confirmation + +## Implementation Details + +### 1. Package Dependencies + +- `express`: REST API framework +- `dotenv`: Environment variable management +- `apidoc`: API documentation generation +- `ws` or `nostr-tools`: WebSocket client for Nostr relays +- `winston`: Logging (following example pattern) + +### 2. Adapter Layer (`src/adapters/`) + +- **nostr-relay.js**: WebSocket client wrapper + - Connect to configured relay(s) + - Send `EVENT`, `REQ`, `CLOSE` messages + - Handle relay responses (`EVENT`, `OK`, `EOSE`, `CLOSED`, `NOTICE`) + - Manage connection pooling for multiple relays +- **wlogger.js**: Winston-based logger + +### 3. Use Cases (`src/use-cases/`) + +- **publish-event.js**: Validate event, send to relay, return OK response +- **query-events.js**: Stateless query - send REQ, collect events until EOSE, return results +- **manage-subscription.js**: Create/close subscriptions, handle SSE streaming + +### 4. Controllers (`src/controllers/rest-api/`) + +- **event/controller.js**: Handle POST /event +- **req/controller.js**: Handle GET/POST/DELETE /req/:subId +- Express middleware for validation, error handling +- SSE support for subscription endpoint + +### 5. Configuration (`src/config/`) + +- Environment-based config (development, production, test) +- Default relay URL(s) from environment variables +- Port, logging level, etc. + +### 6. Subscription Management + +- Store active subscriptions in memory (Map with subId as key) +- Map subscription IDs to WebSocket connections +- Handle cleanup on DELETE /req/:subId +- Support SSE streaming for POST /req/:subId + +### 7. Examples (`examples/`) + +Refactor sandbox examples to use REST API: + +- `01-create-account/` - Use REST API to publish kind 0 event +- `02-read-posts/` - Use GET /req/:subId for stateless query +- `03-write-post/` - Use POST /event +- `04-read-alice-posts/` - Use GET /req/:subId with author filter +- `14-get-follow-list/` - Use GET /req/:subId with kind 3 filter +- Additional examples as needed + +### 8. API Documentation + +- Use api-doc annotations in controller files +- Generate docs with `npm run docs` +- Follow pattern from clean-architecture example + +## Environment Variables + +- `NOSTR_RELAY_URL`: Default relay WebSocket URL (e.g., `wss://nostr-relay.psfoundation.info`) +- `PORT`: Server port (default: 3000) +- `NODE_ENV`: Environment (development, production, test) +- `LOG_LEVEL`: Logging level (info, debug, error) + +## Key Implementation Notes + +- WebSocket connections: Maintain persistent connections to relay(s) in adapter +- Error handling: Map Nostr relay errors to appropriate HTTP status codes +- Validation: Validate Nostr events before forwarding (event structure, signature) +- SSE: Use Express response.write() for Server-Sent Events in subscription endpoint +- Stateless queries: For GET /req/:subId, collect events until EOSE, then close subscription automatically \ No newline at end of file diff --git a/dev-docs/test-plan-for-rest2nostr.plan.md b/dev-docs/test-plan-for-rest2nostr.plan.md new file mode 100644 index 0000000..4ab92f4 --- /dev/null +++ b/dev-docs/test-plan-for-rest2nostr.plan.md @@ -0,0 +1,161 @@ +# Test Plan for REST2NOSTR Application + +## Overview + +Create unit and integration tests for the REST2NOSTR Express.js application following patterns from `tests/testing-example-code/`. Tests will use mocha, chai, and sinon, and cover all code paths exercised by the examples in `/app/examples/`. + +## Test Structure + +Create test directory structure: + +- `app/test/unit/` - Unit tests with mocked dependencies +- `app/test/integration/` - Integration tests with real dependencies +- `app/test/unit/mocks/` - Mock data for unit tests + +## Unit Tests + +### 1. Entity Tests (`test/unit/entities/`) + +- **event-unit.js**: Test Event entity validation and serialization +- Test `isValid()` with valid events +- Test `isValid()` with invalid events (missing fields, wrong types, wrong lengths) +- Test `toJSON()` serialization +- Use mock event data from `mocks/event-mocks.js` + +### 2. Use Case Tests (`test/unit/use-cases/`) + +- **publish-event-unit.js**: Test PublishEventUseCase +- Mock NostrRelayAdapter.sendEvent() +- Test successful event publishing +- Test invalid event rejection +- Test adapter error handling +- Use mocks from `mocks/nostr-relay-mocks.js` + +- **query-events-unit.js**: Test QueryEventsUseCase +- Mock NostrRelayAdapter.sendReq() and sendClose() +- Test successful query with events returned +- Test query timeout handling +- Test CLOSED message handling +- Test EOSE handling + +- **manage-subscription-unit.js**: Test ManageSubscriptionUseCase +- Mock NostrRelayAdapter.sendReq() and sendClose() +- Test subscription creation +- Test subscription closure +- Test duplicate subscription prevention +- Test handler callbacks (onEvent, onEose, onClosed) + +### 3. Controller Tests (`test/unit/controllers/`) + +- **event-controller-unit.js**: Test EventRESTControllerLib +- Mock UseCases.publishEvent.execute() +- Test POST /event with valid event data +- Test POST /event with missing event data +- Test error handling +- Use Express request/response mocks + +- **req-controller-unit.js**: Test ReqRESTControllerLib +- Mock UseCases.queryEvents.execute() and manageSubscription methods +- Test GET /req/:subId with filters (JSON string and parsed) +- Test GET /req/:subId with individual query params +- Test POST /req/:subId for SSE subscription +- Test DELETE /req/:subId for closing subscription +- Test error handling for invalid filters +- Test missing subscription ID validation + +### 4. Adapter Tests (`test/unit/adapters/`) + +- **nostr-relay-unit.js**: Test NostrRelayAdapter +- Mock WebSocket connections +- Test connection establishment +- Test sendEvent() and OK response handling +- Test sendReq() and EVENT/EOSE/CLOSED message handling +- Test sendClose() +- Test message queuing when disconnected +- Test reconnection logic +- Test error handling + +### 5. Server Tests (`test/unit/bin/`) + +- **server-unit.js**: Test Server class +- Mock Express app, Controllers, and adapters +- Test server initialization +- Test middleware attachment +- Test route attachment +- Test error handling +- Test health check endpoint + +## Integration Tests + +### 1. API Endpoint Tests (`test/integration/api/`) + +- **event-integration.js**: Test POST /event endpoint +- Create a test server instance +- Test publishing kind 0 event (profile metadata) - covers example 01 +- Test publishing kind 1 event (text post) - covers example 03 +- Test publishing kind 3 event (follow list) - covers example 06 +- Test publishing kind 7 event (reaction/like) - covers example 07 +- Test invalid event rejection +- Use real Nostr relay connection (may need test relay or mock relay) + +- **req-integration.js**: Test GET /req/:subId endpoint +- Create a test server instance +- Test querying kind 1 events (posts) - covers examples 02, 04 +- Test querying kind 3 events (follow list) - covers example 05 +- Test with various filter combinations +- Test with filters as JSON string query param +- Test with individual query params +- Test error handling + +- **subscription-integration.js**: Test POST /req/:subId SSE subscription +- Create a test server instance +- Test SSE subscription creation +- Test event streaming +- Test EOSE handling +- Test subscription closure +- Test DELETE /req/:subId endpoint + +### 2. Use Case Integration Tests (`test/integration/use-cases/`) + +- **publish-event-integration.js**: Test PublishEventUseCase with real adapter +- **query-events-integration.js**: Test QueryEventsUseCase with real adapter +- **manage-subscription-integration.js**: Test ManageSubscriptionUseCase with real adapter + +## Mock Data Files + +Create mock data files in `test/unit/mocks/`: + +- **event-mocks.js**: Mock event data for various event kinds (0, 1, 3, 7) +- **nostr-relay-mocks.js**: Mock responses from Nostr relay (OK, EVENT, EOSE, CLOSED) +- **controller-mocks.js**: Mock Express request/response objects + +## Key Considerations + +1. **ES Modules**: The app uses ES modules (import/export) while the example uses CommonJS. Tests should use ES modules with `.js` extension and proper import syntax. + +2. **Test Environment**: Integration tests will need a running Nostr relay. Consider: + +- Using a test relay URL from config +- Or creating a mock WebSocket server for integration tests +- Or documenting that tests require a relay URL in environment + +3. **Test Coverage**: Ensure tests cover: + +- All endpoints used by examples (POST /event, GET /req/:subId) +- All event kinds (0, 1, 3, 7) +- Error paths and edge cases +- Validation logic + +4. **Test Data**: Use the same test keys and data patterns from examples where applicable (e.g., Alice's private key, Bob's public key) + +5. **Async Handling**: Properly handle async/await in tests, especially for WebSocket operations and SSE streams + +6. **Cleanup**: Ensure proper cleanup of WebSocket connections and subscriptions in tests + +## Test Execution + +Tests will run using existing npm scripts: + +- `npm test` - Runs unit tests with linting and coverage +- `npm run test:integration` - Runs integration tests with extended timeout +- `npm run coverage` - Generates coverage report \ No newline at end of file diff --git a/dev-docs/unit-test-prompt.md b/dev-docs/unit-test-prompt.md new file mode 100644 index 0000000..984197d --- /dev/null +++ b/dev-docs/unit-test-prompt.md @@ -0,0 +1,13 @@ +The express.js app in the `app/` directory needs unit and integration tests. The scope of this task is to create a plan for adding these + +When considering the creation of tests, follow the patters provided in the example available in the `testing/testing-example-code/` directory. This is simple node.js application which uses the following testing libraries: + +- mocha as a test runner +- chai as an assertion library +- sinon as a stubing library + +The code example show common patterns for writing both unit and integration tests. + +The `/app/examples/` directory contains working code that I've tested and verified works correctly. The unit and integration tests you create should cover the same code paths exercised by these examples. + +The testing dependencies are already installed and listed in the package.json file under the `app/` directory. The package.json file also contains the `test`, `test:integration`, and `coverage` scripts that we'll use to run tests and measure test coverage. \ No newline at end of file diff --git a/examples/01-create-account.js b/examples/01-create-account.js new file mode 100644 index 0000000..3f82033 --- /dev/null +++ b/examples/01-create-account.js @@ -0,0 +1,67 @@ +/* + Example script creating a key-pair for a Nostr account and publishing profile metadata. + Refactored to use REST API instead of WebSocket. + + Run the server with `npm start` in the main directory, before running this example. +*/ + +import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure' +import * as nip19 from 'nostr-tools/nip19' +import { bytesToHex } from '@noble/hashes/utils.js' + +const API_URL = process.env.API_URL || 'http://localhost:5942' + +// Generate keys +const sk = generateSecretKey() // `sk` is a Uint8Array +const nsec = nip19.nsecEncode(sk) +const skHex = bytesToHex(sk) + +const pk = getPublicKey(sk) // `pk` is a hex string +const npub = nip19.npubEncode(pk) + +console.log('private key:', skHex) +console.log('encoded private key:', nsec) +console.log() +console.log('public key:', pk) +console.log('encoded public key:', npub) +console.log() + +// Create profile metadata event (kind 0) +const profileMetadata = { + name: 'Alice', + about: 'Hello, I am Alice!', + picture: 'https://example.com/alice.jpg' +} + +const eventTemplate = { + kind: 0, + created_at: Math.floor(Date.now() / 1000), + tags: [], + content: JSON.stringify(profileMetadata) +} + +// Sign the event +const signedEvent = finalizeEvent(eventTemplate, sk) +console.log('Signed event:', JSON.stringify(signedEvent, null, 2)) + +// Publish to REST API +try { + const response = await fetch(`${API_URL}/event`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(signedEvent) + }) + + const result = await response.json() + console.log('Publish result:', result) + + if (result.accepted) { + console.log('Profile metadata published successfully!') + } else { + console.error('Failed to publish:', result.message) + } +} catch (err) { + console.error('Error publishing event:', err) +} diff --git a/examples/02-read-posts.js b/examples/02-read-posts.js new file mode 100644 index 0000000..4776a13 --- /dev/null +++ b/examples/02-read-posts.js @@ -0,0 +1,44 @@ +/* + Example script for reading posts (kind 1 events) from a relay. + Refactored to use REST API instead of WebSocket. + + Run the server with `npm start` in the main directory, before running this example. +*/ + +const API_URL = process.env.API_URL || 'http://localhost:5942' + +// JB55's public key +const jb55 = '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245' + +// Create subscription ID +const subId = 'read-posts-' + Date.now() + +// Create filters - read posts from JB55 +const filters = { + limit: 2, + kinds: [1], + authors: [jb55] +} + +try { + // Query events using GET /req/:subId + const filtersJson = encodeURIComponent(JSON.stringify([filters])) + const url = `${API_URL}/req/${subId}?filters=${filtersJson}` + + console.log(`Querying events from ${API_URL}`) + console.log('Filters:', JSON.stringify(filters, null, 2)) + + const response = await fetch(url) + const events = await response.json() + + console.log(`\nReceived ${events.length} events:`) + events.forEach((ev, index) => { + console.log(`\nEvent ${index + 1}:`) + console.log(' ID:', ev.id) + console.log(' Author:', ev.pubkey) + console.log(' Created:', new Date(ev.created_at * 1000).toISOString()) + console.log(' Content:', ev.content) + }) +} catch (err) { + console.error('Error reading posts:', err) +} diff --git a/examples/03-write-post.js b/examples/03-write-post.js new file mode 100644 index 0000000..4721444 --- /dev/null +++ b/examples/03-write-post.js @@ -0,0 +1,55 @@ +/* + Example script for writing a post to a relay. + Refactored to use REST API instead of WebSocket. + + Run the server with `npm start` in the main directory, before running this example. +*/ + +import { finalizeEvent, getPublicKey } from 'nostr-tools/pure' +import { hexToBytes } from '@noble/hashes/utils.js' + +const API_URL = process.env.API_URL || 'http://localhost:5942' + +// Alice is our user making the post. +const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0' +const alicePrivKeyBin = hexToBytes(alicePrivKeyHex) +const alicePubKey = getPublicKey(alicePrivKeyBin) +console.log(`Alice Public Key: ${alicePubKey}`) + +const now = new Date() + +// Generate a post. +const eventTemplate = { + kind: 1, + created_at: Math.floor(Date.now() / 1000), + tags: [], + content: `This is a test message posted at ${now.toLocaleString()}` +} +console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`) + +// Sign the post +const signedEvent = finalizeEvent(eventTemplate, alicePrivKeyBin) +console.log('signedEvent:', JSON.stringify(signedEvent, null, 2)) + +// Publish to REST API +try { + const response = await fetch(`${API_URL}/event`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(signedEvent) + }) + + const result = await response.json() + console.log('result:', result) + + if (result.accepted) { + console.log('Post published successfully!') + console.log('Event ID:', result.eventId) + } else { + console.error('Failed to publish:', result.message) + } +} catch (err) { + console.error('Error publishing post:', err) +} diff --git a/examples/04-read-alice-posts.js b/examples/04-read-alice-posts.js new file mode 100644 index 0000000..1636f18 --- /dev/null +++ b/examples/04-read-alice-posts.js @@ -0,0 +1,49 @@ +/* + Example script for reading posts from user Alice. + Refactored to use REST API instead of WebSocket. + + Run the server with `npm start` in the main directory, before running this example. +*/ + +import { getPublicKey } from 'nostr-tools/pure' +import { hexToBytes } from '@noble/hashes/utils.js' + +const API_URL = process.env.API_URL || 'http://localhost:5942' + +// Alice is our user. +const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0' +const alicePrivKeyBin = hexToBytes(alicePrivKeyHex) +const alicePubKey = getPublicKey(alicePrivKeyBin) +console.log(`Alice Public Key: ${alicePubKey}`) + +// Create subscription ID +const subId = 'read-alice-posts-' + Date.now() + +// Create filters - read posts from Alice +const filters = { + limit: 2, + kinds: [1], + authors: [alicePubKey] +} + +try { + // Query events using GET /req/:subId + const filtersJson = encodeURIComponent(JSON.stringify([filters])) + const url = `${API_URL}/req/${subId}?filters=${filtersJson}` + + console.log(`Querying events from ${API_URL}`) + console.log('Filters:', JSON.stringify(filters, null, 2)) + + const response = await fetch(url) + const events = await response.json() + + console.log(`\nReceived ${events.length} events from Alice:`) + events.forEach((ev, index) => { + console.log(`\nEvent ${index + 1}:`) + console.log(' ID:', ev.id) + console.log(' Created:', new Date(ev.created_at * 1000).toISOString()) + console.log(' Content:', ev.content) + }) +} catch (err) { + console.error('Error reading Alice posts:', err) +} diff --git a/examples/05-get-follow-list.js b/examples/05-get-follow-list.js new file mode 100644 index 0000000..7ef397d --- /dev/null +++ b/examples/05-get-follow-list.js @@ -0,0 +1,53 @@ +/* + Example script for getting a follow list (kind 3 events). + Refactored to use REST API instead of WebSocket. + + Run the server with `npm start` in the main directory, before running this example. +*/ + +import { getPublicKey } from 'nostr-tools/pure' +import { hexToBytes } from '@noble/hashes/utils.js' + +const API_URL = process.env.API_URL || 'http://localhost:5942' + +// Alice is our user to get the follow list. +const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0' +const alicePrivKeyBin = hexToBytes(alicePrivKeyHex) +const alicePubKey = getPublicKey(alicePrivKeyBin) +console.log(`Alice Public Key: ${alicePubKey}`) + +// Create subscription ID +const subId = 'get-follow-list-' + Date.now() + +// Create filters - get follow list (kind 3) from Alice +const filters = { + limit: 5, + kinds: [3], + authors: [alicePubKey] +} + +try { + // Query events using GET /req/:subId + const filtersJson = encodeURIComponent(JSON.stringify([filters])) + const url = `${API_URL}/req/${subId}?filters=${filtersJson}` + + console.log(`Querying follow list from ${API_URL}`) + console.log('Filters:', JSON.stringify(filters, null, 2)) + + const response = await fetch(url) + const events = await response.json() + + if (events.length > 0) { + // Get the most recent follow list (kind 3 events are replaceable) + const followListEvent = events[0] + const aliceFollowList = followListEvent.tags.filter(tag => tag[0] === 'p') + console.log(`\nAlice Follow list (${aliceFollowList.length} followed users):`) + aliceFollowList.forEach((tag, index) => { + console.log(` ${index + 1}. ${tag[1]}${tag[3] ? ` (${tag[3]})` : ''}`) + }) + } else { + console.log('\nNo follow list found for Alice') + } +} catch (err) { + console.error('Error getting follow list:', err) +} diff --git a/examples/06-update-follow-list.js b/examples/06-update-follow-list.js new file mode 100644 index 0000000..292e241 --- /dev/null +++ b/examples/06-update-follow-list.js @@ -0,0 +1,63 @@ +/* + Example to update the follow list with a new list of people to follow. + Refactored to use REST API instead of WebSocket. + + Run the server with `npm start` in the main directory, before running this example. +*/ + +import { finalizeEvent, getPublicKey } from 'nostr-tools/pure' +import { hexToBytes } from '@noble/hashes/utils.js' + +const API_URL = process.env.API_URL || 'http://localhost:5942' + +// Alice wants to update her follow list +const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0' +const alicePrivKeyBin = hexToBytes(alicePrivKeyHex) +const alicePubKey = getPublicKey(alicePrivKeyBin) +console.log(`Alice Public Key: ${alicePubKey}`) + +// Bob is the person to be added to the new follow list +const bobPrivKeyHex = 'd2e71a977bc3900d6b0f787421e3d1a666cd12ca625482b0d9eeffd23489c99f' +const bobPrivKeyBin = hexToBytes(bobPrivKeyHex) +const bobPubKey = getPublicKey(bobPrivKeyBin) +console.log(`Bob Public Key: ${bobPubKey}`) + +const psf = 'wss://nostr-relay.psfoundation.info' + +const followList = [ + ['p', bobPubKey, psf, 'bob'] +] + +// Generate a follow list event (kind 3) +const eventTemplate = { + kind: 3, + created_at: Math.floor(Date.now() / 1000), + tags: followList, + content: '' +} +console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`) + +// Sign the event +const signedEvent = finalizeEvent(eventTemplate, alicePrivKeyBin) + +// Publish to REST API +try { + const response = await fetch(`${API_URL}/event`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(signedEvent) + }) + + const result = await response.json() + console.log('Publish result:', result) + + if (result.accepted) { + console.log('Follow list updated successfully!') + } else { + console.error('Failed to update follow list:', result.message) + } +} catch (err) { + console.error('Error updating follow list:', err) +} diff --git a/examples/07-liking-event.js b/examples/07-liking-event.js new file mode 100644 index 0000000..5c0a685 --- /dev/null +++ b/examples/07-liking-event.js @@ -0,0 +1,59 @@ +/* + Example script for adding a reaction (like) to an event. + Refactored to use REST API instead of WebSocket. + https://github.com/nostr-protocol/nips/blob/master/25.md + + Run the server with `npm start` in the main directory, before running this example. +*/ + +import { hexToBytes } from '@noble/hashes/utils.js' +import { finalizeEvent, getPublicKey } from 'nostr-tools/pure' + +const API_URL = process.env.API_URL || 'http://localhost:5942' + +const bobPrivKeyHex = 'd2e71a977bc3900d6b0f787421e3d1a666cd12ca625482b0d9eeffd23489c99f' +const bobPrivKeyBin = hexToBytes(bobPrivKeyHex) +const bobPubKey = getPublicKey(bobPrivKeyBin) + +const psf = 'wss://nostr-relay.psfoundation.info' + +const evIdToLike = 'd09b4c5da59be3cd2768aa53fa78b77bf4859084c94f3bf26d401f004a9c8167' +const evIdAuthorPubKey = '2c7e76c0f8dc1dca9d0197c7d19be580a8d074ccada6a2f6ebe056ae41092e92' + +// Generate like event (kind 7) +const likeEventTemplate = { + kind: 7, + created_at: Math.floor(Date.now() / 1000), + pubkey: bobPubKey, + tags: [ + ['e', evIdToLike, psf], // "e" tag includes event id, relay reference + ['p', evIdAuthorPubKey, psf] // "p" tag includes author pubkey, relay reference + ], + content: '+' +} + +// Sign the event +const signedEvent = finalizeEvent(likeEventTemplate, bobPrivKeyBin) +console.log('signedEvent:', JSON.stringify(signedEvent, null, 2)) + +// Publish to REST API +try { + const response = await fetch(`${API_URL}/event`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(signedEvent) + }) + + const result = await response.json() + console.log('result:', result) + + if (result.accepted) { + console.log('Like published successfully!') + } else { + console.error('Failed to publish like:', result.message) + } +} catch (err) { + console.error('Error publishing like:', err) +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..d840c99 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,90 @@ +# REST2NOSTR Examples + +This directory contains examples refactored from the `nostr-sandbox/` directory to use the REST API instead of WebSocket connections. + +## Prerequisites + +1. Install dependencies: +```bash +npm install nostr-tools @noble/hashes +``` + +2. Start the REST2NOSTR proxy server: +```bash +npm start +``` + +3. Set the API_URL environment variable if the server is not running on localhost:3000: +```bash +export API_URL=http://localhost:3000 +``` + +## Examples + +### 01-create-account.js +Creates a new Nostr account keypair and publishes profile metadata (kind 0 event). + +```bash +node examples/01-create-account.js +``` + +### 02-read-posts.js +Reads posts (kind 1 events) from a specific author using GET /req/:subId. + +```bash +node examples/02-read-posts.js +``` + +### 03-write-post.js +Publishes a text post (kind 1 event) using POST /event. + +```bash +node examples/03-write-post.js +``` + +### 04-read-alice-posts.js +Reads posts from Alice's account using GET /req/:subId with author filter. + +```bash +node examples/04-read-alice-posts.js +``` + +### 14-get-follow-list.js +Retrieves a user's follow list (kind 3 event) using GET /req/:subId. + +```bash +node examples/05-get-follow-list.js +``` + +### 15-update-follow-list.js +Updates a user's follow list (kind 3 event) using POST /event. + +```bash +node examples/06-update-follow-list.js +``` + +### 17-liking-event.js +Adds a reaction/like to an event (kind 7 event) using POST /event. + +```bash +node examples/07-liking-event.js +``` + +## API Endpoints Used + +- **POST /event**: Publish events to the relay +- **GET /req/:subId**: Stateless query for events (returns immediately) + +## Differences from WebSocket Examples + +1. **No WebSocket connections**: All communication is via HTTP REST API +2. **Stateless queries**: GET /req/:subId returns events immediately rather than streaming +3. **Event publishing**: POST /event returns immediately with acceptance status +4. **No subscription management**: For stateless queries, subscriptions are automatically closed after EOSE + +## Notes + +- These examples use the same private keys as the original sandbox examples for consistency +- The REST API handles WebSocket connections to relays internally +- For real-time streaming, use POST /req/:subId which supports Server-Sent Events (SSE) + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fc2d34c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10834 @@ +{ + "name": "psf-bch-api", + "version": "7.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "psf-bch-api", + "version": "7.0.0", + "license": "MIT", + "dependencies": { + "@psf/bch-js": "7.1.11", + "axios": "1.7.7", + "cors": "2.8.5", + "dotenv": "16.3.1", + "express": "5.1.0", + "minimal-slp-wallet": "7.1.4", + "psffpp": "1.2.1", + "slp-token-media": "1.2.10", + "winston": "3.11.0", + "winston-daily-rotate-file": "4.7.1", + "x402-bch-express": "2.0.0" + }, + "devDependencies": { + "apidoc": "1.2.0", + "c8": "10.1.3", + "chai": "6.2.0", + "mocha": "11.7.4", + "sinon": "21.0.0", + "standard": "17.1.2" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@chris.troutner/bip32-utils": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@chris.troutner/bip32-utils/-/bip32-utils-1.0.5.tgz", + "integrity": "sha512-pa9dh5VpPmfol1bdLy+FyqONmxlf/QH6Q01a57OP6C9gTVOZM1Rt0kCLXxXKC6e2AnNIrXpYN1UtlyBm+r6P0g==", + "license": "MIT", + "dependencies": { + "keccak": "^3.0.1", + "tape": "*" + }, + "engines": { + "node": ">=10.15.1" + } + }, + "node_modules/@chris.troutner/bitcore-lib-cash": { + "version": "8.25.26", + "resolved": "https://registry.npmjs.org/@chris.troutner/bitcore-lib-cash/-/bitcore-lib-cash-8.25.26.tgz", + "integrity": "sha512-WPLW2od7VJsAifN1gPa01XPWpVbDC4pPEVwYXbXZ764NFF6qq1mEcAqnmhWVS1ov130qEx3wjdKpRkEJGoqlVg==", + "license": "MIT", + "dependencies": { + "bitcore-lib": "^8.25.25", + "bn.js": "=4.11.8", + "bs58": "^4.0.1", + "buffer-compare": "=1.1.1", + "elliptic": "^6.5.3", + "inherits": "=2.0.1", + "lodash": "^4.17.20" + } + }, + "node_modules/@chris.troutner/bitcore-lib-cash/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", + "license": "ISC" + }, + "node_modules/@chris.troutner/retry-queue": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@chris.troutner/retry-queue/-/retry-queue-1.0.11.tgz", + "integrity": "sha512-IJt19IdG4oy20PnHvRHn1bPrMHCwzPuL2R8Ze13VbaKQXKYjcBV5neARn/TQJ/V2It1z2OkV0qj/sJV7scahaA==", + "license": "MIT", + "dependencies": { + "p-queue": "7.3.0", + "p-retry": "5.1.1" + } + }, + "node_modules/@chris.troutner/retry-queue-commonjs": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@chris.troutner/retry-queue-commonjs/-/retry-queue-commonjs-1.0.8.tgz", + "integrity": "sha512-jEHmCKffjIXTm0d/YcRQzCDPbzW2NFTEO72b1vmETnmATO57MmO39F37gEaq7J7lnF5JMxuM3Oq+FWa1L7HXxA==", + "license": "MIT", + "dependencies": { + "p-queue": "4.0.0", + "p-retry": "4.6.2" + } + }, + "node_modules/@chris.troutner/retry-queue-commonjs/node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@chris.troutner/retry-queue-commonjs/node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "license": "MIT" + }, + "node_modules/@chris.troutner/retry-queue-commonjs/node_modules/p-queue": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-4.0.0.tgz", + "integrity": "sha512-3cRXXn3/O0o3+eVmUroJPSj/esxoEFIm0ZOno/T+NzG/VZgPOqQ8WKmlNqubSEpZmCIngEy34unkHGg83ZIBmg==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^3.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@chris.troutner/retry-queue-commonjs/node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", + "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@ljharb/resumer": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@ljharb/resumer/-/resumer-0.1.3.tgz", + "integrity": "sha512-d+tsDgfkj9X5QTriqM4lKesCkMMJC3IrbPKHvayP00ELx2axdXvDfWkqjxrLXIzGcQzmj7VAUT1wopqARTvafw==", + "license": "MIT", + "dependencies": { + "@ljharb/through": "^2.3.13", + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@ljharb/through": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.14.tgz", + "integrity": "sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@psf/bch-js": { + "version": "7.1.11", + "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-7.1.11.tgz", + "integrity": "sha512-gDJCaY2aG8EtUWcR9D4tbiyM1BkHd7gkuO7NRaLpwdbSrnU51lyPr69mjQLKOcVlL+aXdJsOjL5pm15t4zq18w==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincashjs-lib": "4.0.3", + "@psf/coininfo": "4.0.0", + "axios": "1.13.2", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.14", + "bigi": "1.4.2", + "bignumber.js": "9.3.1", + "bip-schnorr": "0.6.7", + "bip38": "3.1.1", + "bip39": "3.1.0", + "bip66": "1.1.5", + "bitcoinjs-message": "2.2.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "6.0.0", + "randombytes": "2.1.0", + "safe-buffer": "5.2.1", + "satoshi-bitcoin": "1.0.5", + "slp-mdm": "0.0.7", + "slp-parser": "0.0.4", + "wif": "2.0.6", + "x402-bch-axios": "2.2.1" + } + }, + "node_modules/@psf/bch-js/node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@psf/bip21": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@psf/bip21/-/bip21-2.0.1.tgz", + "integrity": "sha512-U9c8xBV31n+D7qxOPBO0vQ015DNvKskWCUbVgoMfH5AUNHLYrSDWIrCx4P7v9etfdu6LpPdsYr53KDSAIk0b7Q==", + "license": "ISC", + "dependencies": { + "qs": "^6.3.0" + } + }, + "node_modules/@psf/bitcoincash-ops": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@psf/bitcoincash-ops/-/bitcoincash-ops-2.0.0.tgz", + "integrity": "sha512-M3PWqRpeJq6rli2NqWGbas76z9TrdGOmNuDFACBWBMctPucEAsFQY2AmyFHRSa7hEwythwvrPh9AG/n6ehmEog==", + "license": "MIT" + }, + "node_modules/@psf/bitcoincashjs-lib": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@psf/bitcoincashjs-lib/-/bitcoincashjs-lib-4.0.3.tgz", + "integrity": "sha512-sJYi7jYUqR7S+Z8TjN3W/5Lcju7xcIBxYLhIEpnOF+jR9kMt0ftpVjQBt8vVsJZUD8ArL9bf59oDETjdOUcdGQ==", + "license": "MIT", + "dependencies": { + "@psf/bitcoincash-ops": "^2.0.0", + "@psf/pushdata-bitcoin": "^1.2.2", + "bech32": "^1.1.2", + "bigi": "^1.4.0", + "bip66": "^1.1.0", + "bs58check": "^2.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.3", + "ecurve": "^1.0.0", + "merkle-lib": "^2.0.10", + "randombytes": "^2.0.1", + "safe-buffer": "^5.0.1", + "typeforce": "^1.18.0", + "varuint-bitcoin": "^1.0.4", + "wif": "^2.0.1" + }, + "engines": { + "node": ">=10.15.1" + } + }, + "node_modules/@psf/coininfo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@psf/coininfo/-/coininfo-4.0.0.tgz", + "integrity": "sha512-RwBc09790kbaOt8uZJMyvLqf1UziTd20FXu78bM8bMlkClnZQTJyNDdLCsFSBkJQYAJtGMkjdQ/o3/UaSC7c2Q==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/@psf/pushdata-bitcoin": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@psf/pushdata-bitcoin/-/pushdata-bitcoin-1.2.2.tgz", + "integrity": "sha512-e1qkZLJFU6Ldg7TMBgSkiR5U1NfpRgSIr2ppk8BeED/Q9wUc9DEVAjXfPMD65xgXdqnlyFEtLbpPMWwyydMVUQ==", + "license": "MIT", + "dependencies": { + "@psf/bitcoincash-ops": "^2.0.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", + "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", + "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "license": "MIT", + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "license": "MIT", + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "license": "MIT", + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apidoc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-1.2.0.tgz", + "integrity": "sha512-Qagoj7QnqNHbDUDNpU21eLP4hJSAXn6knHtEjRYWTlSEmpYDGSQijejyFXaWGByhfryW8B1gL+6B57UB/F1lxw==", + "dev": true, + "license": "MIT", + "os": [ + "darwin", + "freebsd", + "linux", + "openbsd", + "win32" + ], + "dependencies": { + "bootstrap": "3.4.1", + "commander": "^10.0.0", + "diff-match-patch": "^1.0.5", + "esbuild-loader": "^2.16.0", + "expose-loader": "^4.0.0", + "fs-extra": "^11.0.0", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "iconv-lite": "^0.6.3", + "jquery": "^3.6.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "markdown-it": "^12.2.0", + "nodemon": "^3.0.1", + "prismjs": "^1.25.0", + "semver": "^7.5.0", + "style-loader": "^3.3.1", + "webpack": "^5.64.2", + "webpack-cli": "^4.9.1", + "winston": "^3.3.3" + }, + "bin": { + "apidoc": "bin/apidoc" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.every": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/array.prototype.every/-/array.prototype.every-1.1.7.tgz", + "integrity": "sha512-BIP72rKvrKd08ptbetLb4qvrlGjkv30yOKgKcTtOIbHyQt3shr/jyOzdApiCOh3LPYrpJo5M6i0zmVldOF2pUw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "is-string": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.15", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", + "integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bc-bip68": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bc-bip68/-/bc-bip68-1.0.5.tgz", + "integrity": "sha512-GzaMlN7pNthrY5BhReVhnfr4Ixx+GUSfyNRHYh0QiMUF0d0+0YaD8MpEdv6AjFBksg/zlqL1fVCBBm6PpTt2Rg==", + "license": "ISC", + "engines": { + "node": ">=4.5.0" + } + }, + "node_modules/bch-consumer": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/bch-consumer/-/bch-consumer-1.6.2.tgz", + "integrity": "sha512-PclXVzmWtwqxe2ba6Mo9QbqxJ+5rUog8c+Ro5PV/W3IxkKNotVRa71bLei99AIELDO3A3ljfXkoCNsSM7tR37A==", + "license": "MIT", + "dependencies": { + "@psf/bch-js": "6.7.3", + "apidoc": "0.51.0", + "axios": "0.25.0" + } + }, + "node_modules/bch-consumer/node_modules/@psf/bch-js": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.7.3.tgz", + "integrity": "sha512-z6oJvPAXxSObPhqUAxCkCuPL2bp3Bs1oyhBZpQ5SR6cbJzcOMhuI4bHho2srH5agSCU+bbjehBMRGvo18eGW0A==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincash-ops": "2.0.0", + "@psf/bitcoincashjs-lib": "4.0.3", + "@psf/coininfo": "4.0.0", + "axios": "0.26.1", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.5", + "bigi": "1.4.2", + "bignumber.js": "9.0.0", + "bip-schnorr": "0.3.0", + "bip38": "2.0.2", + "bip39": "3.0.2", + "bip66": "1.1.5", + "bitcoinjs-message": "2.0.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "1.3.8", + "randombytes": "2.0.6", + "safe-buffer": "5.1.2", + "satoshi-bitcoin": "1.0.4", + "slp-mdm": "0.0.6", + "slp-parser": "0.0.4", + "wif": "2.0.6" + } + }, + "node_modules/bch-consumer/node_modules/@psf/bch-js/node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/bch-consumer/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", + "license": "MIT" + }, + "node_modules/bch-consumer/node_modules/apidoc": { + "version": "0.51.0", + "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.51.0.tgz", + "integrity": "sha512-3P4srhm6NA+kE/YRM4qL5jESElpQQJL+Z8n7hyr4uC+1DSwGzmE6adXPVxuT/hpDyShrqmRlHk8hf40RSqEsNw==", + "license": "MIT", + "os": [ + "darwin", + "freebsd", + "linux", + "openbsd", + "win32" + ], + "dependencies": { + "bootstrap": "3.4.1", + "commander": "^8.3.0", + "diff-match-patch": "^1.0.5", + "esbuild-loader": "^2.16.0", + "expose-loader": "^3.1.0", + "fs-extra": "^10.0.0", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "iconv-lite": "^0.6.3", + "jquery": "^3.6.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "markdown-it": "^12.2.0", + "nodemon": "^2.0.15", + "path-to-regexp": "^6.2.0", + "prismjs": "^1.25.0", + "semver": "^7.3.5", + "style-loader": "^3.3.1", + "url-parse": "^1.5.3", + "webpack": "^5.64.2", + "webpack-cli": "^4.9.1", + "winston": "^3.3.3" + }, + "bin": { + "apidoc": "bin/apidoc" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/bch-consumer/node_modules/axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.7" + } + }, + "node_modules/bch-consumer/node_modules/bchaddrjs-slp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz", + "integrity": "sha512-33flmPcqMFswerKu7477DSUNMVMQR3tHDk3lvbmsdkEva+TxVGGWWE/p5Lqx9M/8t3vkbe7fzmVhj4QhChcCyA==", + "license": "MIT", + "dependencies": { + "bs58check": "^2.1.2", + "cashaddrjs-slp": "^0.2.11" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/bch-consumer/node_modules/big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bch-consumer/node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bch-consumer/node_modules/bip-schnorr": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.3.0.tgz", + "integrity": "sha512-Sc1Hn2+1n+okPEW8G+JLjeaM12dsUOwr+oFlMDSKR9wYwNGMw0alskeBIHTmXxBxMZSWKhCW7PwKQVDyGmnaVg==", + "license": "MIT", + "dependencies": { + "ecurve": "^1.0.6", + "js-sha256": "^0.9.0", + "random-bytes": "^1.0.0", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bch-consumer/node_modules/bip38": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bip38/-/bip38-2.0.2.tgz", + "integrity": "sha512-22KDak0RDyghFbR0Si7wyq9IgY423YzGYzWLpGeofH3DaolOQqjD3mNN08eFoubKlbyclOQKFwtONMv2SD9V3A==", + "dependencies": { + "bigi": "^1.2.0", + "browserify-aes": "^1.0.1", + "bs58check": "<3.0.0", + "buffer-xor": "^1.0.2", + "create-hash": "^1.1.1", + "ecurve": "^1.0.0", + "scryptsy": "^2.0.0" + } + }, + "node_modules/bch-consumer/node_modules/bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "license": "ISC", + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "node_modules/bch-consumer/node_modules/bitcoinjs-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz", + "integrity": "sha512-H5pJC7/eSqVjREiEOZ4jifX+7zXYP3Y28GIOIqg9hrgE7Vj8Eva9+HnVqnxwA1rJPOwZKuw0vo6k0UxgVc6q1A==", + "license": "MIT", + "dependencies": { + "bs58check": "^2.0.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "^3.0.1", + "varuint-bitcoin": "^1.0.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/bch-consumer/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/bch-consumer/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/bch-consumer/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/bch-consumer/node_modules/expose-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz", + "integrity": "sha512-2RExSo0yJiqP+xiUue13jQa2IHE8kLDzTI7b6kn+vUlBVvlzNSiLDzo4e5Pp5J039usvTUnxZ8sUOhv0Kg15NA==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/bch-consumer/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/bch-consumer/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bch-consumer/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bch-consumer/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/bch-consumer/node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/bch-consumer/node_modules/nodemon/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bch-consumer/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/bch-consumer/node_modules/randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/bch-consumer/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/bch-consumer/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/bch-consumer/node_modules/satoshi-bitcoin": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/satoshi-bitcoin/-/satoshi-bitcoin-1.0.4.tgz", + "integrity": "sha512-YuHOmw5wsz6wuHIQdsz5b2cgtuKtV/jEcZ4NGmWN5tM/gz5T9Q+DSuLlnuf5BP/jsQWgR0ofmY4f8Dm6JnqSug==", + "license": "MIT", + "dependencies": { + "big.js": "^3.1.3" + } + }, + "node_modules/bch-consumer/node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "license": "MIT", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/bch-consumer/node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/bch-consumer/node_modules/slp-mdm": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/slp-mdm/-/slp-mdm-0.0.6.tgz", + "integrity": "sha512-fbjlIg/o8OtzgK2JydC6POJp3Qup/rLgy4yB5hoLgxWRlERyJyE29ScwS3r9TTwPxe12qK55pyivAdNOZZXL0A==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/bch-consumer/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bch-donation": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bch-donation/-/bch-donation-1.1.2.tgz", + "integrity": "sha512-V7xQ23M6Ocavb1Nj4/pATwaLX/uF/9s2nWir3f+F3aetD3kMmsRHFRuXJwGQG3dSC5fJtRR/M5Z2zynY3t91Xg==", + "license": "MIT", + "dependencies": { + "apidoc": "0.51.0" + } + }, + "node_modules/bch-donation/node_modules/apidoc": { + "version": "0.51.0", + "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.51.0.tgz", + "integrity": "sha512-3P4srhm6NA+kE/YRM4qL5jESElpQQJL+Z8n7hyr4uC+1DSwGzmE6adXPVxuT/hpDyShrqmRlHk8hf40RSqEsNw==", + "license": "MIT", + "os": [ + "darwin", + "freebsd", + "linux", + "openbsd", + "win32" + ], + "dependencies": { + "bootstrap": "3.4.1", + "commander": "^8.3.0", + "diff-match-patch": "^1.0.5", + "esbuild-loader": "^2.16.0", + "expose-loader": "^3.1.0", + "fs-extra": "^10.0.0", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "iconv-lite": "^0.6.3", + "jquery": "^3.6.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "markdown-it": "^12.2.0", + "nodemon": "^2.0.15", + "path-to-regexp": "^6.2.0", + "prismjs": "^1.25.0", + "semver": "^7.3.5", + "style-loader": "^3.3.1", + "url-parse": "^1.5.3", + "webpack": "^5.64.2", + "webpack-cli": "^4.9.1", + "winston": "^3.3.3" + }, + "bin": { + "apidoc": "bin/apidoc" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/bch-donation/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/bch-donation/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/bch-donation/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/bch-donation/node_modules/expose-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz", + "integrity": "sha512-2RExSo0yJiqP+xiUue13jQa2IHE8kLDzTI7b6kn+vUlBVvlzNSiLDzo4e5Pp5J039usvTUnxZ8sUOhv0Kg15NA==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/bch-donation/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/bch-donation/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bch-donation/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bch-donation/node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/bch-donation/node_modules/nodemon/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bch-donation/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/bch-donation/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/bch-donation/node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "license": "MIT", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/bch-donation/node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/bch-donation/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bchaddrjs-slp": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.14.tgz", + "integrity": "sha512-4EdUV6Kwu2Ae8QyYtLAnHOUu0Zfl0w5wDPbeftiu3+fVr6ZyPmuVKlZtU2HERx76BOngZpGTf+suM3nuPJ0Tuw==", + "license": "MIT", + "dependencies": { + "bs58check": "^2.1.2", + "cashaddrjs-slp": "^0.2.12" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bigi": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", + "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip-schnorr": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.6.7.tgz", + "integrity": "sha512-Pf1o+whA52l7NC33CZY4eRtcB+dUCT54hKCF8mnw2349CG89LOXODHRWmUEcSSWzoi+Kjg1CtMmw6uV43tmJFA==", + "license": "MIT", + "dependencies": { + "bigi": "^1.4.2", + "ecurve": "^1.0.6", + "js-sha256": "^0.9.0", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bip38": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bip38/-/bip38-3.1.1.tgz", + "integrity": "sha512-d5AQWuXS95rdrEBnaKeZ9osMkvDR3QDULbAfoE7xRw0W8jfDrza9CsZwf+UhqjV6lbbfaWCrJIu5uObJlfo5Rw==", + "dependencies": { + "bigi": "^1.2.0", + "browserify-aes": "^1.0.1", + "bs58check": "<3.0.0", + "buffer-xor": "^1.0.2", + "create-hash": "^1.1.1", + "ecurve": "^1.0.0", + "safe-buffer": "~5.1.1", + "scryptsy": "^2.1.0" + } + }, + "node_modules/bip38/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "license": "ISC", + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, + "node_modules/bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bitcoinjs-message": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.2.0.tgz", + "integrity": "sha512-103Wy3xg8Y9o+pdhGP4M3/mtQQuUWs6sPuOp1mYphSUoSMHjHTlkj32K4zxU8qMH0Ckv23emfkGlFWtoWZ7YFA==", + "license": "MIT", + "dependencies": { + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "^3.0.1", + "varuint-bitcoin": "^1.0.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/bitcore-lib": { + "version": "8.25.47", + "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.47.tgz", + "integrity": "sha512-qDZr42HuP4P02I8kMGZUx/vvwuDsz8X3rQxXLfM0BtKzlQBcbSM7ycDkDN99Xc5jzpd4fxNQyyFXOmc6owUsrQ==", + "license": "MIT", + "dependencies": { + "bech32": "=2.0.0", + "bip-schnorr": "=0.6.4", + "bn.js": "=4.11.8", + "bs58": "^4.0.1", + "buffer-compare": "=1.1.1", + "elliptic": "^6.5.3", + "inherits": "=2.0.1", + "lodash": "^4.17.20" + } + }, + "node_modules/bitcore-lib/node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT" + }, + "node_modules/bitcore-lib/node_modules/bip-schnorr": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.6.4.tgz", + "integrity": "sha512-dNKw7Lea8B0wMIN4OjEmOk/Z5qUGqoPDY0P2QttLqGk1hmDPytLWW8PR5Pb6Vxy6CprcdEgfJpOjUu+ONQveyg==", + "license": "MIT", + "dependencies": { + "bigi": "^1.4.2", + "ecurve": "^1.0.6", + "js-sha256": "^0.9.0", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcore-lib/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", + "license": "ISC" + }, + "node_modules/bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bootstrap": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", + "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==", + "deprecated": "This version of Bootstrap is no longer supported. Please upgrade to the latest version.", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "license": "MIT", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/buffer-compare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-compare/-/buffer-compare-1.1.1.tgz", + "integrity": "sha512-O6NvNiHZMd3mlIeMDjP6t/gPG75OqGPeiRZXoMQZJ6iy9GofCls4Ijs5YkPZZwoysizLiedhticmdyx/GyHghA==" + }, + "node_modules/buffer-equals": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz", + "integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/cashaddrjs-slp": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/cashaddrjs-slp/-/cashaddrjs-slp-0.2.12.tgz", + "integrity": "sha512-n2TTIuW6vZZxYvjvsUAA+wOM0Zkj+3RRKUtDC1XSu4Ic4XVr0yFJkl1bzQkHWda7nkVT51sxjZneygz7D0SyrQ==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.34" + } + }, + "node_modules/chai": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", + "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", + "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dotignore": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", + "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.4" + }, + "bin": { + "ignored": "bin/ignored" + } + }, + "node_modules/drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecashaddrjs": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ecashaddrjs/-/ecashaddrjs-1.0.7.tgz", + "integrity": "sha512-KsvHYLlYtLr/GBkEPiwwQDIDBzqRx61qC34n1puHKOjVE4Uwg3syHccjFCqNynLa6T6xI0Rd7ByCRUJcuJcoIw==", + "license": "MIT", + "dependencies": { + "big-integer": "1.6.36" + } + }, + "node_modules/ecashaddrjs/node_modules/big-integer": { + "version": "1.6.36", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz", + "integrity": "sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ecurve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", + "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", + "license": "MIT", + "dependencies": { + "bigi": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "license": "ISC" + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", + "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.16.17", + "@esbuild/android-arm64": "0.16.17", + "@esbuild/android-x64": "0.16.17", + "@esbuild/darwin-arm64": "0.16.17", + "@esbuild/darwin-x64": "0.16.17", + "@esbuild/freebsd-arm64": "0.16.17", + "@esbuild/freebsd-x64": "0.16.17", + "@esbuild/linux-arm": "0.16.17", + "@esbuild/linux-arm64": "0.16.17", + "@esbuild/linux-ia32": "0.16.17", + "@esbuild/linux-loong64": "0.16.17", + "@esbuild/linux-mips64el": "0.16.17", + "@esbuild/linux-ppc64": "0.16.17", + "@esbuild/linux-riscv64": "0.16.17", + "@esbuild/linux-s390x": "0.16.17", + "@esbuild/linux-x64": "0.16.17", + "@esbuild/netbsd-x64": "0.16.17", + "@esbuild/openbsd-x64": "0.16.17", + "@esbuild/sunos-x64": "0.16.17", + "@esbuild/win32-arm64": "0.16.17", + "@esbuild/win32-ia32": "0.16.17", + "@esbuild/win32-x64": "0.16.17" + } + }, + "node_modules/esbuild-loader": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-2.21.0.tgz", + "integrity": "sha512-k7ijTkCT43YBSZ6+fBCW1Gin7s46RrJ0VQaM8qA7lq7W+OLsGgtLyFV8470FzYi/4TeDexniTBTPTwZUnXXR5g==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.16.17", + "joycon": "^3.0.1", + "json5": "^2.2.0", + "loader-utils": "^2.0.0", + "tapable": "^2.2.0", + "webpack-sources": "^1.4.3" + }, + "funding": { + "url": "https://github.com/privatenumber/esbuild-loader?sponsor=1" + }, + "peerDependencies": { + "webpack": "^4.40.0 || ^5.0.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-config-standard-jsx": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", + "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peerDependencies": { + "eslint": "^8.8.0", + "eslint-plugin-react": "^7.28.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/expose-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-4.1.0.tgz", + "integrity": "sha512-oLAesnzerwDGGADzBMnu0LPqqnlVz6e2V9lTa+/4X6VeW9W93x/nJpw05WBrcIdbqXm/EdnEQpiVDFFiQXuNfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.1" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-dynamic-import": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/has-dynamic-import/-/has-dynamic-import-2.1.1.tgz", + "integrity": "sha512-DuTCn6K/RW8S27npDMumGKsjG6HE7MxzedZka5tJP+9dqfxks+UMqKBmeCijHtIhsBEZPlbMg0qMHi2nKYVtKQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/hash-base/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hash-base/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hash-base/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/hash-base/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT" + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keccak": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/load-json-file/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=6" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merkle-lib": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz", + "integrity": "sha512-XrNQvUbn1DL5hKNe46Ccs+Tu3/PYOlrcZILuGUhb95oKBPjc/nmIC8D462PQkipVDGKRvwhn+QFg2cCdIvmDJA==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimal-slp-wallet": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/minimal-slp-wallet/-/minimal-slp-wallet-7.1.4.tgz", + "integrity": "sha512-oHPDu+dUyAT8YOyKvb6JGZYIoalXAEHvcAUby7OgL3D/5R+cR21FTjpqpp7BJp+NFcGRANohnLAqpP2vt6tgFA==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue": "1.0.11", + "@psf/bch-js": "7.1.11", + "bch-consumer": "1.6.2", + "bch-donation": "1.1.2", + "crypto-js": "4.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "11.7.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.4.tgz", + "integrity": "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mock-property": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mock-property/-/mock-property-1.1.0.tgz", + "integrity": "sha512-1/JjbLoGwv87xVsutkX0XJc0M0W4kb40cZl/K41xtTViBOD9JuFPKfyMNTrLJ/ivYAd0aPqu/vduamXO0emTFQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "functions-have-names": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.2", + "isarray": "^2.0.5", + "object-inspect": "^1.13.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", + "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.3.0.tgz", + "integrity": "sha512-5fP+yVQ0qp0rEfZoDTlP2c3RYBgxvRsw30qO+VtPPc95lyvSG+x6USSh1TuLB4n96IO6I8/oXQGsTgtna4q2nQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.7", + "p-timeout": "^5.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-5.1.1.tgz", + "integrity": "sha512-i69WkEU5ZAL8mrmdmVviWwU+DN+IUF8f4sSJThoJ3z5A7Nn5iuO5ROX3Boye0u+uYQLOSfgFl7SuFZCjlAVbQA==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.1", + "retry": "^0.13.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p2wdb": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/p2wdb/-/p2wdb-2.2.10.tgz", + "integrity": "sha512-6X/SgkO2FSBlL5i0GWqEeuORQ2APji7w8ufkH2xR4YFJ9GtChcaV8Jdk6dNwVaJa+9bT3vPGwpFx93rttDxV8g==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue-commonjs": "1.0.8", + "apidoc": "0.52.0", + "axios": "0.24.0" + } + }, + "node_modules/p2wdb/node_modules/apidoc": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.52.0.tgz", + "integrity": "sha512-k0gaMI7LWxaqKt2D+twC6XpI8X5SHkJalfo8TmF72d2TSNABquqB8LyIrVYzLcadcHuLMRFDaDibOK271gD67w==", + "license": "MIT", + "os": [ + "darwin", + "freebsd", + "linux", + "openbsd", + "win32" + ], + "dependencies": { + "bootstrap": "3.4.1", + "commander": "^8.3.0", + "diff-match-patch": "^1.0.5", + "esbuild-loader": "^2.16.0", + "expose-loader": "^3.1.0", + "fs-extra": "^10.0.0", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "iconv-lite": "^0.6.3", + "jquery": "^3.6.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "markdown-it": "^12.2.0", + "nodemon": "^2.0.15", + "prismjs": "^1.25.0", + "semver": "^7.3.5", + "style-loader": "^3.3.1", + "webpack": "^5.64.2", + "webpack-cli": "^4.9.1", + "winston": "^3.3.3" + }, + "bin": { + "apidoc": "bin/apidoc" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/p2wdb/node_modules/axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, + "node_modules/p2wdb/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/p2wdb/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/p2wdb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/p2wdb/node_modules/expose-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz", + "integrity": "sha512-2RExSo0yJiqP+xiUue13jQa2IHE8kLDzTI7b6kn+vUlBVvlzNSiLDzo4e5Pp5J039usvTUnxZ8sUOhv0Kg15NA==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/p2wdb/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/p2wdb/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/p2wdb/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p2wdb/node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/p2wdb/node_modules/nodemon/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/p2wdb/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/p2wdb/node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "license": "MIT", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/p2wdb/node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/p2wdb/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-conf/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/psf-multisig-approval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/psf-multisig-approval/-/psf-multisig-approval-2.1.0.tgz", + "integrity": "sha512-QLlOnYzeOx6OQXu5MGRlSP/IhS+X1Y7v3WxUse9riqYHOvvz3WeUPZUB8TVnSsUR6lH5W8Ckshl1lnaWXcPs7Q==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bitcore-lib-cash": "8.25.26", + "axios": "1.3.5" + } + }, + "node_modules/psf-multisig-approval/node_modules/axios": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz", + "integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/psffpp": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/psffpp/-/psffpp-1.2.1.tgz", + "integrity": "sha512-CVOMI5Y2rAv5B+U816a6d4Zs7mVH3V4m5kenz0bcwqDscd1Z2r+GAAq6yZLmj8aOo2inniUNHW0dMU7pM6xrHg==", + "license": "MIT", + "dependencies": { + "axios": "1.3.5", + "psf-multisig-approval": "2.1.0" + } + }, + "node_modules/psffpp/node_modules/axios": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz", + "integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "license": "MIT", + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/satoshi-bitcoin": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/satoshi-bitcoin/-/satoshi-bitcoin-1.0.5.tgz", + "integrity": "sha512-iCUOSe8yTOqetYcj/n4ziv0psqRMRZT4+YhcMrQIYpjnx3Pgn9uiTlaUItNMMB/0sldINEGkJvxwTW55OfrYPg==", + "license": "MIT", + "dependencies": { + "big.js": "^3.1.3" + } + }, + "node_modules/satoshi-bitcoin/node_modules/big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", + "license": "MIT" + }, + "node_modules/secp256k1": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.1.tgz", + "integrity": "sha512-tArjQw2P0RTdY7QmkNehgp6TVvQXq6ulIhxv8gaH6YubKG/wxxAoNKcbuXjDhybbc+b2Ihc7e0xxiGN744UIiQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.5.7", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sinon": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.5", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/slp-mdm": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/slp-mdm/-/slp-mdm-0.0.7.tgz", + "integrity": "sha512-XlnDS7y8fnEt9I5lw/GqcrDjTGw5vZon83xlliLyihz2EvjE135ubIBzq4bmjVOrM669G9A6bkqEp5Y1trPV3A==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/slp-mutable-data": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/slp-mutable-data/-/slp-mutable-data-2.3.10.tgz", + "integrity": "sha512-lyqY1gSjHQqLfJq1dDZUS7dTmYUTTk3j6Z4HXDAQ4X6vDsm7yYvXNKgHdiWueP0Jb0f1z1SJwJ/hZP6todch5g==", + "license": "GPL-2.0", + "dependencies": { + "axios": "0.27.2", + "p2wdb": "2.2.10" + } + }, + "node_modules/slp-mutable-data/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/slp-parser": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slp-parser/-/slp-parser-0.0.4.tgz", + "integrity": "sha512-AvbslJumkzGfMGWNvuE2pWx2nyHEk/VgQ7l119kDKIFRTuRUWOkyOULLauw5laGRQsBRThg6NCx/TsR3grX6GA==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/slp-token-media": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/slp-token-media/-/slp-token-media-1.2.10.tgz", + "integrity": "sha512-+/QPMuLax9fN1aUYT+ftyx47P224OTQgJLXxzifB9ZC+m3rO5ItcOFC36naZDMTMzHYMld25FyU7N/1W3qfpPQ==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue-commonjs": "1.0.8", + "axios": "0.27.2", + "slp-mutable-data": "2.3.10" + } + }, + "node_modules/slp-token-media/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/standard": { + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.2.tgz", + "integrity": "sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "eslint": "^8.41.0", + "eslint-config-standard": "17.1.0", + "eslint-config-standard-jsx": "^11.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-react": "^7.36.1", + "standard-engine": "^15.1.0", + "version-guard": "^1.1.1" + }, + "bin": { + "standard": "bin/cmd.cjs" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/standard-engine": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz", + "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "get-stdin": "^8.0.0", + "minimist": "^1.2.6", + "pkg-conf": "^3.1.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tape": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/tape/-/tape-5.9.0.tgz", + "integrity": "sha512-czbGgxSVwRlbB3Ly/aqQrNwrDAzKHDW/kVXegp4hSFmR2c8qqm3hCgZbUy1+3QAQFGhPDG7J56UsV1uNilBFCA==", + "license": "MIT", + "dependencies": { + "@ljharb/resumer": "^0.1.3", + "@ljharb/through": "^2.3.13", + "array.prototype.every": "^1.1.6", + "call-bind": "^1.0.7", + "deep-equal": "^2.2.3", + "defined": "^1.0.1", + "dotignore": "^0.1.2", + "for-each": "^0.3.3", + "get-package-type": "^0.1.0", + "glob": "^7.2.3", + "has-dynamic-import": "^2.1.0", + "hasown": "^2.0.2", + "inherits": "^2.0.4", + "is-regex": "^1.1.4", + "minimist": "^1.2.8", + "mock-property": "^1.1.0", + "object-inspect": "^1.13.2", + "object-is": "^1.1.6", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "resolve": "^2.0.0-next.5", + "string.prototype.trim": "^1.2.9" + }, + "bin": { + "tape": "bin/tape" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tape/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==", + "license": "MIT" + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/version-guard": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.3.tgz", + "integrity": "sha512-JwPr6erhX53EWH/HCSzfy1tTFrtPXUe927wdM1jqBBeYp1OM+qPHjWbsvv6pIBduqdgxxS+ScfG7S28pzyr2DQ==", + "dev": true, + "license": "0BSD", + "engines": { + "node": ">=0.10.48" + } + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "license": "MIT", + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" + }, + "node_modules/winston": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", + "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz", + "integrity": "sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==", + "license": "MIT", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^2.0.1", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/x402-bch-axios": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/x402-bch-axios/-/x402-bch-axios-2.2.1.tgz", + "integrity": "sha512-N23DpQRA746bqGDTCJYVQ8QL55PoxE6LTSNMFzx5sySsvnBem6H0hQLSf2N9e45O8xPVw3ulSvXncgTcVM8ikw==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue": "1.0.11", + "minimal-slp-wallet": "7.0.5" + } + }, + "node_modules/x402-bch-axios/node_modules/@psf/bch-js": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-7.1.4.tgz", + "integrity": "sha512-/+5mbg6RFybKPNtB/m0NEOJYVyT4Rosy/GHGjHknOEkBRF2Xd0/pZTtPAd+BtvzePDki4c2KyL13VkF5XghFyw==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincashjs-lib": "4.0.3", + "@psf/coininfo": "4.0.0", + "axios": "1.13.2", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.14", + "bigi": "1.4.2", + "bignumber.js": "9.3.1", + "bip-schnorr": "0.6.7", + "bip38": "3.1.1", + "bip39": "3.1.0", + "bip66": "1.1.5", + "bitcoinjs-message": "2.2.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "6.0.0", + "randombytes": "2.1.0", + "safe-buffer": "5.2.1", + "satoshi-bitcoin": "1.0.5", + "slp-mdm": "0.0.7", + "slp-parser": "0.0.4", + "wif": "2.0.6", + "x402-bch-axios": "2.1.0" + } + }, + "node_modules/x402-bch-axios/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", + "license": "MIT" + }, + "node_modules/x402-bch-axios/node_modules/apidoc": { + "version": "0.51.0", + "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.51.0.tgz", + "integrity": "sha512-3P4srhm6NA+kE/YRM4qL5jESElpQQJL+Z8n7hyr4uC+1DSwGzmE6adXPVxuT/hpDyShrqmRlHk8hf40RSqEsNw==", + "license": "MIT", + "os": [ + "darwin", + "freebsd", + "linux", + "openbsd", + "win32" + ], + "dependencies": { + "bootstrap": "3.4.1", + "commander": "^8.3.0", + "diff-match-patch": "^1.0.5", + "esbuild-loader": "^2.16.0", + "expose-loader": "^3.1.0", + "fs-extra": "^10.0.0", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "iconv-lite": "^0.6.3", + "jquery": "^3.6.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "markdown-it": "^12.2.0", + "nodemon": "^2.0.15", + "path-to-regexp": "^6.2.0", + "prismjs": "^1.25.0", + "semver": "^7.3.5", + "style-loader": "^3.3.1", + "url-parse": "^1.5.3", + "webpack": "^5.64.2", + "webpack-cli": "^4.9.1", + "winston": "^3.3.3" + }, + "bin": { + "apidoc": "bin/apidoc" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/x402-bch-axios/node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/x402-bch-axios/node_modules/big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/x402-bch-axios/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/x402-bch-axios/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/x402-bch-axios/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/x402-bch-axios/node_modules/expose-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz", + "integrity": "sha512-2RExSo0yJiqP+xiUue13jQa2IHE8kLDzTI7b6kn+vUlBVvlzNSiLDzo4e5Pp5J039usvTUnxZ8sUOhv0Kg15NA==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/x402-bch-axios/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/x402-bch-axios/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/x402-bch-axios/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/x402-bch-axios/node_modules/minimal-slp-wallet": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/minimal-slp-wallet/-/minimal-slp-wallet-7.0.5.tgz", + "integrity": "sha512-yrWQwXrtUZYvB6mWe3HTBzQGK1oEjgHuXkanx94tejPR3N8nJLmRRMQu27oWIYD/m3wm0wU8UreYXPIA0iN/iw==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue": "1.0.11", + "@psf/bch-js": "7.1.4", + "bch-consumer": "1.6.2", + "bch-donation": "1.1.2", + "crypto-js": "4.0.0" + } + }, + "node_modules/x402-bch-axios/node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/x402-bch-axios/node_modules/nodemon/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/x402-bch-axios/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/x402-bch-axios/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/x402-bch-axios/node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "license": "MIT", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/x402-bch-axios/node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/x402-bch-axios/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/x402-bch-axios/-/x402-bch-axios-2.1.0.tgz", + "integrity": "sha512-6IE6tUmg/+W4Poi6JBs5z0xjSOhvouzea9IH6BUnuBIdXDm0wv5cwT0cN5qAA4k7BE7hiB98LPiG6QIIkoGjVA==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue": "1.0.11", + "minimal-slp-wallet": "7.0.2" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/@psf/bch-js": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-7.1.1.tgz", + "integrity": "sha512-Wknq+q418Zb2Hmb6aDmxt/6VLrU81N+Iq9KP0KHGo1lFHeupJJonBkVt74R1ulry61F7MNRr39iDCTQsMM0ctg==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincashjs-lib": "4.0.3", + "@psf/coininfo": "4.0.0", + "axios": "1.13.2", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.14", + "bigi": "1.4.2", + "bignumber.js": "9.3.1", + "bip-schnorr": "0.6.7", + "bip38": "3.1.1", + "bip39": "3.1.0", + "bip66": "1.1.5", + "bitcoinjs-message": "2.2.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "6.0.0", + "randombytes": "2.1.0", + "safe-buffer": "5.2.1", + "satoshi-bitcoin": "1.0.5", + "slp-mdm": "0.0.7", + "slp-parser": "0.0.4", + "wif": "2.0.6", + "x402-bch-axios": "1.1.1" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/minimal-slp-wallet": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/minimal-slp-wallet/-/minimal-slp-wallet-7.0.2.tgz", + "integrity": "sha512-H3H46VXXhvHF/1EKIaAVpBXevX5Bmt1SYqpfQFIcb7JSajXwwncQv7gmhf5Q9H3jtwdY43YWq2TFoeLUkI7E4g==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue": "1.0.11", + "@psf/bch-js": "7.1.1", + "bch-consumer": "1.6.2", + "bch-donation": "1.1.2", + "crypto-js": "4.0.0" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/x402-bch-axios/-/x402-bch-axios-1.1.1.tgz", + "integrity": "sha512-jQ0AyjAzvyTw7ejTTvUSIQiH8//wVn/SRwSgcJ3LeM38SJlJWpTMvvNpOpcjcZ+kFKOe4SsurOy/AcH7o+akjQ==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue": "1.0.11", + "minimal-slp-wallet": "6.1.0" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/@psf/bch-js": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.8.1.tgz", + "integrity": "sha512-xu5YT9L3OhdILwJUmkV7Pg/WZ3r2aA6jD7fr5WMvo0OJSzzBBbXGShIbQNZp9+Vv9BoeVLCMqpZoW1Fp7OLCbg==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincash-ops": "2.0.0", + "@psf/bitcoincashjs-lib": "4.0.3", + "@psf/coininfo": "4.0.0", + "axios": "0.26.1", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.5", + "bigi": "1.4.2", + "bignumber.js": "9.0.0", + "bip-schnorr": "0.3.0", + "bip38": "2.0.2", + "bip39": "3.0.2", + "bip66": "1.1.5", + "bitcoinjs-message": "2.0.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "1.3.8", + "randombytes": "2.0.6", + "safe-buffer": "5.1.2", + "satoshi-bitcoin": "1.0.4", + "slp-mdm": "0.0.6", + "slp-parser": "0.0.4", + "wif": "2.0.6" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/bchaddrjs-slp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz", + "integrity": "sha512-33flmPcqMFswerKu7477DSUNMVMQR3tHDk3lvbmsdkEva+TxVGGWWE/p5Lqx9M/8t3vkbe7fzmVhj4QhChcCyA==", + "license": "MIT", + "dependencies": { + "bs58check": "^2.1.2", + "cashaddrjs-slp": "^0.2.11" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/bip-schnorr": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.3.0.tgz", + "integrity": "sha512-Sc1Hn2+1n+okPEW8G+JLjeaM12dsUOwr+oFlMDSKR9wYwNGMw0alskeBIHTmXxBxMZSWKhCW7PwKQVDyGmnaVg==", + "license": "MIT", + "dependencies": { + "ecurve": "^1.0.6", + "js-sha256": "^0.9.0", + "random-bytes": "^1.0.0", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/bip38": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bip38/-/bip38-2.0.2.tgz", + "integrity": "sha512-22KDak0RDyghFbR0Si7wyq9IgY423YzGYzWLpGeofH3DaolOQqjD3mNN08eFoubKlbyclOQKFwtONMv2SD9V3A==", + "dependencies": { + "bigi": "^1.2.0", + "browserify-aes": "^1.0.1", + "bs58check": "<3.0.0", + "buffer-xor": "^1.0.2", + "create-hash": "^1.1.1", + "ecurve": "^1.0.0", + "scryptsy": "^2.0.0" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "license": "ISC", + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/bitcoinjs-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz", + "integrity": "sha512-H5pJC7/eSqVjREiEOZ4jifX+7zXYP3Y28GIOIqg9hrgE7Vj8Eva9+HnVqnxwA1rJPOwZKuw0vo6k0UxgVc6q1A==", + "license": "MIT", + "dependencies": { + "bs58check": "^2.0.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "^3.0.1", + "varuint-bitcoin": "^1.0.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/minimal-slp-wallet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/minimal-slp-wallet/-/minimal-slp-wallet-6.1.0.tgz", + "integrity": "sha512-P24WzJu2kHg5aQtKkIVJ42Gu1dsYXWAc0z5MCmlinjDq11Bgy4ICBu73YxpNTB3TQwrWIIi2LJgsuQa/dqH50Q==", + "license": "MIT", + "dependencies": { + "@chris.troutner/retry-queue-commonjs": "1.0.8", + "@psf/bch-js": "6.8.1", + "apidoc": "0.51.0", + "bch-consumer": "1.6.2", + "bch-donation": "1.1.2", + "crypto-js": "4.0.0" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/satoshi-bitcoin": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/satoshi-bitcoin/-/satoshi-bitcoin-1.0.4.tgz", + "integrity": "sha512-YuHOmw5wsz6wuHIQdsz5b2cgtuKtV/jEcZ4NGmWN5tM/gz5T9Q+DSuLlnuf5BP/jsQWgR0ofmY4f8Dm6JnqSug==", + "license": "MIT", + "dependencies": { + "big.js": "^3.1.3" + } + }, + "node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/x402-bch-axios/node_modules/slp-mdm": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/slp-mdm/-/slp-mdm-0.0.6.tgz", + "integrity": "sha512-fbjlIg/o8OtzgK2JydC6POJp3Qup/rLgy4yB5hoLgxWRlERyJyE29ScwS3r9TTwPxe12qK55pyivAdNOZZXL0A==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/x402-bch-express": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/x402-bch-express/-/x402-bch-express-2.0.0.tgz", + "integrity": "sha512-RB1HteZhUkmI5q5kISpzh00pTiuMfi/AUhTllT5Kz9LVng8dHAIlAU7kr/cuntRKfnU/ELzikXPyATY+bzhLEg==", + "license": "MIT", + "dependencies": { + "@psf/bch-js": "6.8.3" + } + }, + "node_modules/x402-bch-express/node_modules/@psf/bch-js": { + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.8.3.tgz", + "integrity": "sha512-LhbfRg9c5GFddNyEHvZ7QWB61452hZ8xygaZ8JJHw8HJBMvuURwaHItv971A6LvO8nl4Fcf9GPTJ5YIggnGhVQ==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincash-ops": "2.0.0", + "@psf/bitcoincashjs-lib": "4.0.3", + "@psf/coininfo": "4.0.0", + "axios": "1.12.2", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.5", + "bigi": "1.4.2", + "bignumber.js": "9.0.0", + "bip-schnorr": "0.3.0", + "bip38": "2.0.2", + "bip39": "3.0.2", + "bip66": "1.1.5", + "bitcoinjs-message": "2.0.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "1.3.8", + "randombytes": "2.0.6", + "safe-buffer": "5.1.2", + "satoshi-bitcoin": "1.0.4", + "slp-mdm": "0.0.6", + "slp-parser": "0.0.4", + "wif": "2.0.6" + } + }, + "node_modules/x402-bch-express/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", + "license": "MIT" + }, + "node_modules/x402-bch-express/node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/x402-bch-express/node_modules/bchaddrjs-slp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz", + "integrity": "sha512-33flmPcqMFswerKu7477DSUNMVMQR3tHDk3lvbmsdkEva+TxVGGWWE/p5Lqx9M/8t3vkbe7fzmVhj4QhChcCyA==", + "license": "MIT", + "dependencies": { + "bs58check": "^2.1.2", + "cashaddrjs-slp": "^0.2.11" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/x402-bch-express/node_modules/big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/x402-bch-express/node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/x402-bch-express/node_modules/bip-schnorr": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.3.0.tgz", + "integrity": "sha512-Sc1Hn2+1n+okPEW8G+JLjeaM12dsUOwr+oFlMDSKR9wYwNGMw0alskeBIHTmXxBxMZSWKhCW7PwKQVDyGmnaVg==", + "license": "MIT", + "dependencies": { + "ecurve": "^1.0.6", + "js-sha256": "^0.9.0", + "random-bytes": "^1.0.0", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/x402-bch-express/node_modules/bip38": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bip38/-/bip38-2.0.2.tgz", + "integrity": "sha512-22KDak0RDyghFbR0Si7wyq9IgY423YzGYzWLpGeofH3DaolOQqjD3mNN08eFoubKlbyclOQKFwtONMv2SD9V3A==", + "dependencies": { + "bigi": "^1.2.0", + "browserify-aes": "^1.0.1", + "bs58check": "<3.0.0", + "buffer-xor": "^1.0.2", + "create-hash": "^1.1.1", + "ecurve": "^1.0.0", + "scryptsy": "^2.0.0" + } + }, + "node_modules/x402-bch-express/node_modules/bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "license": "ISC", + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "node_modules/x402-bch-express/node_modules/bitcoinjs-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz", + "integrity": "sha512-H5pJC7/eSqVjREiEOZ4jifX+7zXYP3Y28GIOIqg9hrgE7Vj8Eva9+HnVqnxwA1rJPOwZKuw0vo6k0UxgVc6q1A==", + "license": "MIT", + "dependencies": { + "bs58check": "^2.0.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "^3.0.1", + "varuint-bitcoin": "^1.0.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/x402-bch-express/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/x402-bch-express/node_modules/randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/x402-bch-express/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/x402-bch-express/node_modules/satoshi-bitcoin": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/satoshi-bitcoin/-/satoshi-bitcoin-1.0.4.tgz", + "integrity": "sha512-YuHOmw5wsz6wuHIQdsz5b2cgtuKtV/jEcZ4NGmWN5tM/gz5T9Q+DSuLlnuf5BP/jsQWgR0ofmY4f8Dm6JnqSug==", + "license": "MIT", + "dependencies": { + "big.js": "^3.1.3" + } + }, + "node_modules/x402-bch-express/node_modules/slp-mdm": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/slp-mdm/-/slp-mdm-0.0.6.tgz", + "integrity": "sha512-fbjlIg/o8OtzgK2JydC6POJp3Qup/rLgy4yB5hoLgxWRlERyJyE29ScwS3r9TTwPxe12qK55pyivAdNOZZXL0A==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..29e267d --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "psf-bch-api", + "version": "7.0.0", + "main": "psf-bch-api.js", + "type": "module", + "scripts": { + "start": "node bin/server.js", + "docs": "./node_modules/.bin/apidoc -i src/ -o docs", + "lint": "standard --env mocha --fix", + "test": "npm run lint && TEST=unit c8 mocha 'test/unit/**/*.js' --exit", + "test:integration": "mocha --timeout 25000 'test/integration/**/*.js' --exit", + "coverage": "c8 --reporter=html mocha 'test/unit/**/*.js' --exit" + }, + "author": "Chris Troutner ", + "license": "MIT", + "description": "REST API proxy to Bitcoin Cash infrastructure", + "dependencies": { + "@psf/bch-js": "7.1.11", + "axios": "1.7.7", + "cors": "2.8.5", + "dotenv": "16.3.1", + "express": "5.1.0", + "minimal-slp-wallet": "7.1.4", + "psffpp": "1.2.1", + "slp-token-media": "1.2.10", + "winston": "3.11.0", + "winston-daily-rotate-file": "4.7.1", + "x402-bch-express": "2.0.0" + }, + "devDependencies": { + "apidoc": "1.2.0", + "c8": "10.1.3", + "chai": "6.2.0", + "mocha": "11.7.4", + "sinon": "21.0.0", + "standard": "17.1.2" + }, + "apidoc": { + "title": "psf-bch-api", + "url": "http://localhost:3070" + } +} diff --git a/production/docker/.env-example b/production/docker/.env-example new file mode 100644 index 0000000..3d1e320 --- /dev/null +++ b/production/docker/.env-example @@ -0,0 +1,37 @@ +# START INFRASTRUCTURE SETUP + +# Full Node Connection +RPC_BASEURL=http://172.17.0.1:8332 +RPC_USERNAME=bitcoin +RPC_PASSWORD=password + +# Fulcrum Indexer +FULCRUM_API=http://172.17.0.1:3001/v1 + +# SLP Indexer +SLP_INDEXER_API=http://172.17.0.1:5010 + +# REST API URL for wallet operations +LOCAL_RESTURL=http://172.17.0.1:5942/v6 + +# END INFRASTRUCTURE SETUP + + +# START ACCESS CONTROL + +PORT=5942 + +# x402 payments required to access this API? +X402_ENABLED=false +#X402_ENABLED=true +#SERVER_BCH_ADDRESS=bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d +#FACILITATOR_URL=http://localhost:4345/facilitator +#X402_PRICE_SAT=200 + +# Basic Authentication required to access this API? +USE_BASIC_AUTH=false +#USE_BASIC_AUTH=true +#BASIC_AUTH_TOKEN=some-random-token + +# END ACCESS CONTROL + diff --git a/production/docker/Dockerfile b/production/docker/Dockerfile new file mode 100644 index 0000000..b445ac5 --- /dev/null +++ b/production/docker/Dockerfile @@ -0,0 +1,70 @@ +# Create a Dockerized API server +# + +#IMAGE BUILD COMMANDS +FROM ubuntu:22.04 +MAINTAINER Chris Troutner + +#Update the OS and install any OS packages needed. +RUN apt-get update +RUN apt-get install -y sudo git curl nano gnupg wget zip unzip python3 + +#Install Node and NPM +RUN curl -sL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh +RUN bash nodesource_setup.sh +RUN apt-get install -y nodejs build-essential + +#Create the user 'safeuser' and add them to the sudo group. +RUN useradd -ms /bin/bash safeuser +RUN adduser safeuser sudo + +#Set password to 'password' change value below if you want a different password +RUN echo safeuser:password | chpasswd + +#Set the working directory to be the home directory +WORKDIR /home/safeuser + +#Setup NPM for non-root global install +RUN mkdir /home/safeuser/.npm-global +RUN chown -R safeuser .npm-global +RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile +RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'" + +# Update to the latest version of npm. +#RUN npm install -g npm@8.3.0 + +# npm mirror to prevent direct dependency on npm. +#RUN npm set registry http://94.130.170.209:4873/ + +# Switch to user account. +#USER safeuser +# Prep 'sudo' commands. +#RUN echo 'abcd8765' | sudo -S pwd + +#RUN npm install -g node-gyp + +# Clone the rest.bitcoin.com repository +WORKDIR /home/safeuser +RUN git clone https://github.com/Permissionless-Software-Foundation/psf-bch-api + +# Switch to the desired branch. `master` is usually stable, +# and `stage` has the most up-to-date changes. +WORKDIR /home/safeuser/psf-bch-api + +RUN git checkout ct-unstable + +# Install dependencies +RUN npm install +RUN npm install minimal-slp-wallet + +# Generate the API docs +RUN npm run docs + +COPY .env .env + + +CMD ["npm", "start"] + +# Used to debug the container. +#COPY temp.js temp.js +#CMD ["node", "temp.js"] diff --git a/production/docker/cleanup-images.sh b/production/docker/cleanup-images.sh new file mode 100755 index 0000000..405d9f5 --- /dev/null +++ b/production/docker/cleanup-images.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Remove all untagged docker images. +docker rmi $(docker images | grep "^" | awk '{print $3}') + diff --git a/production/docker/docker-compose.yml b/production/docker/docker-compose.yml new file mode 100644 index 0000000..881d8d8 --- /dev/null +++ b/production/docker/docker-compose.yml @@ -0,0 +1,20 @@ +# Start the service with the command 'docker-compose up -d' + +services: + psf-bch-api: + build: . + container_name: psf-bch-api + logging: + driver: 'json-file' + options: + max-size: '10m' + max-file: '10' + #mem_limit: 500mb + #links: + # - mongo-slp-indexer + ports: + - '5942:5942' # : + volumes: + #- ./start-rest2nostr.sh:/home/safeuser/REST2NOSTR/start-rest2nostr.sh + - ./.env:/home/safeuser/.env + restart: always \ No newline at end of file diff --git a/psf-bch-api.js b/psf-bch-api.js new file mode 100644 index 0000000..c8f350b --- /dev/null +++ b/psf-bch-api.js @@ -0,0 +1,11 @@ +/* + Main entry point for REST2NOSTR Proxy API +*/ + +import Server from './bin/server.js' + +const server = new Server() +server.startServer().catch(err => { + console.error('Failed to start server:', err) + process.exit(1) +}) diff --git a/src/adapters/fulcrum-api.js b/src/adapters/fulcrum-api.js new file mode 100644 index 0000000..d39a7f7 --- /dev/null +++ b/src/adapters/fulcrum-api.js @@ -0,0 +1,124 @@ +/* + Adapter library for interacting with Fulcrum API service over HTTP. +*/ + +import axios from 'axios' +import wlogger from './wlogger.js' +import config from '../config/index.js' + +class FulcrumAPIAdapter { + constructor (localConfig = {}) { + this.config = localConfig.config || config + + // Allow missing config for testing environments + if (!this.config.fulcrumApi || !this.config.fulcrumApi.baseUrl) { + if (process.env.NODE_ENV === 'test' || process.env.TEST) { + // In test environment, create a mock baseURL + this.config.fulcrumApi = { + baseUrl: 'http://localhost:50001', + timeoutMs: 15000 + } + } else { + throw new Error('FULCRUM_API env var not set. Can not connect to Fulcrum indexer.') + } + } + + const { + baseUrl, + timeoutMs = 15000 + } = this.config.fulcrumApi + + this.http = axios.create({ + baseURL: baseUrl, + timeout: timeoutMs + }) + } + + async get (path) { + try { + const response = await this.http.get(path) + return response.data + } catch (err) { + throw this._handleError(err) + } + } + + async post (path, data) { + try { + const response = await this.http.post(path, data) + return response.data + } catch (err) { + throw this._handleError(err) + } + } + + _handleError (err) { + const { status, message } = this.decodeError(err) + const error = new Error(message) + error.status = status + error.originalError = err + return error + } + + decodeError (err) { + try { + // Attempt to extract error message from response data + if (err.response && err.response.data) { + const data = err.response.data + // Handle structured error responses + if (data.error) { + return this._formatError(data.error, err.response.status || 400) + } + // Handle string error messages + if (typeof data === 'string') { + return this._formatError(data, err.response.status || 400) + } + // Handle object responses that might contain error info + if (typeof data === 'object' && data.message) { + return this._formatError(data.message, err.response.status || 400) + } + // Fallback to returning the status + return this._formatError('Fulcrum API error', err.response.status || 500) + } + + // Network errors + if (err.message) { + if (err.message.includes('ENOTFOUND') || err.message.includes('ENETUNREACH') || err.message.includes('EAI_AGAIN')) { + return this._formatError( + 'Network error: Could not communicate with Fulcrum API service.', + 503 + ) + } + } + + if (err.code && (err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED')) { + return this._formatError( + 'Network error: Could not communicate with Fulcrum API service.', + 503 + ) + } + + if (err.error && typeof err.error === 'string' && err.error.includes('429')) { + return this._formatError('429 Too Many Requests', 429) + } + + if (err.message) { + return this._formatError(err.message, err.status || 422) + } + + return this._formatError('Unhandled Fulcrum API error', 500) + } catch (decodeError) { + wlogger.error('Unhandled error in FulcrumAPIAdapter.decodeError()', decodeError) + return this._formatError('Internal server error', 500) + } + } + + _formatError (message, status = 500) { + return { + message: message || 'Internal server error', + status: status || 500 + } + } +} + +export default FulcrumAPIAdapter diff --git a/src/adapters/full-node-rpc.js b/src/adapters/full-node-rpc.js new file mode 100644 index 0000000..3704f8e --- /dev/null +++ b/src/adapters/full-node-rpc.js @@ -0,0 +1,129 @@ +/* + Adapter library for interacting with a BCH full node over JSON-RPC. +*/ + +import axios from 'axios' +import wlogger from './wlogger.js' +import config from '../config/index.js' + +class FullNodeRPCAdapter { + constructor (localConfig = {}) { + this.config = localConfig.config || config + + if (!this.config.fullNode || !this.config.fullNode.rpcBaseUrl) { + throw new Error('Full node RPC configuration is required') + } + + const { + rpcBaseUrl, + rpcUsername, + rpcPassword, + rpcTimeoutMs = 15000 + } = this.config.fullNode + + this.requestIdPrefix = this.config.fullNode.rpcRequestIdPrefix || 'psf-bch-api' + + this.http = axios.create({ + baseURL: rpcBaseUrl, + timeout: rpcTimeoutMs, + auth: { + username: rpcUsername, + password: rpcPassword + } + }) + + this.defaultRequestPayload = { + jsonrpc: '1.0' + } + } + + async call (method, params = [], requestId) { + const id = requestId || `${this.requestIdPrefix}-${method}` + + try { + const response = await this.http.post('', { + ...this.defaultRequestPayload, + id, + method, + params + }) + + if (response.data && response.data.error) { + const rpcError = this._formatError(response.data.error.message, 400) + throw rpcError + } + + return response.data.result + } catch (err) { + throw this._handleError(err) + } + } + + _handleError (err) { + const { status, message } = this.decodeError(err) + const error = new Error(message) + error.status = status + error.originalError = err + return error + } + + decodeError (err) { + try { + if ( + err.response && + err.response.data && + err.response.data.error && + err.response.data.error.message + ) { + return this._formatError(err.response.data.error.message, 400) + } + + if (err.response && err.response.data) { + return this._formatError(err.response.data, err.response.status || 500) + } + + if (err.message) { + if (err.message.includes('ENOTFOUND') || err.message.includes('ENETUNREACH') || err.message.includes('EAI_AGAIN')) { + return this._formatError( + 'Network error: Could not communicate with full node or other external service.', + 503 + ) + } + } + + if (err.code && (err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED')) { + return this._formatError( + 'Network error: Could not communicate with full node or other external service.', + 503 + ) + } + + if (err.error && typeof err.error === 'string' && err.error.includes('429')) { + return this._formatError('429 Too Many Requests', 429) + } + + if (err.message) { + return this._formatError(err.message, err.status || 422) + } + + return this._formatError('Unhandled full node error', 500) + } catch (decodeError) { + wlogger.error('Unhandled error in FullNodeRPCAdapter.decodeError()', decodeError) + return this._formatError('Internal server error', 500) + } + } + + validateArraySize (length) { + const limit = 20 + return length <= limit + } + + _formatError (message, status = 500) { + return { + message: message || 'Internal server error', + status: status || 500 + } + } +} + +export default FullNodeRPCAdapter diff --git a/src/adapters/index.js b/src/adapters/index.js new file mode 100644 index 0000000..7d0d02c --- /dev/null +++ b/src/adapters/index.js @@ -0,0 +1,221 @@ +/* + This is a top-level library that encapsulates all the additional Adapters. + The concept of Adapters comes from Clean Architecture: + https://troutsblog.com/blog/clean-architecture +*/ + +// Load individual adapter libraries. +// import NostrRelayAdapter from './nostr-relay.js' +import FullNodeRPCAdapter from './full-node-rpc.js' +import FulcrumAPIAdapter from './fulcrum-api.js' +import SlpIndexerAPIAdapter from './slp-indexer-api.js' +import config from '../config/index.js' + +class Adapters { + constructor (localConfig = {}) { + // Encapsulate dependencies + this.config = config + + // Determine relay URLs: prefer localConfig, fall back to config + // let relayUrls = [] + // if (localConfig.relayUrls && Array.isArray(localConfig.relayUrls)) { + // relayUrls = localConfig.relayUrls + // } else if (localConfig.relayUrl) { + // // Backward compatibility: single relay URL + // relayUrls = [localConfig.relayUrl] + // } else { + // relayUrls = config.nostrRelayUrls + // } + + // Create one adapter per relay URL + // this.nostrRelays = relayUrls.map(relayUrl => new NostrRelayAdapter({ relayUrl })) + + // Maintain backward compatibility: expose first relay as nostrRelay + // This allows existing code to work during transition + // this.nostrRelay = this.nostrRelays[0] + + this.fullNode = new FullNodeRPCAdapter({ config: this.config }) + this.fulcrum = new FulcrumAPIAdapter({ config: this.config }) + this.slpIndexer = new SlpIndexerAPIAdapter({ config: this.config }) + } + + async start () { + // try { + // Connect to all Nostr relays concurrently + // const connectPromises = this.nostrRelays.map(async (relay, index) => { + // try { + // await relay.connect() + // console.log(`Nostr relay adapter ${index + 1}/${this.nostrRelays.length} started: ${relay.relayUrl}`) + // return { success: true, relay } + // } catch (err) { + // console.error(`Failed to connect to relay ${relay.relayUrl}:`, err.message) + // return { success: false, relay, error: err } + // } + // }) + + // const results = await Promise.allSettled(connectPromises) + + // const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length + // const failed = results.length - successful + + // if (successful === 0) { + // throw new Error('Failed to connect to any Nostr relay') + // } + + // if (failed > 0) { + // console.warn(`Connected to ${successful}/${this.nostrRelays.length} relays. Some relays failed to connect.`) + // } else { + // console.log(`All ${successful} Nostr relay adapters started successfully.`) + // } + + return true + // } catch (err) { + // console.error('Error in adapters/index.js/start()') + // throw err + // } + } + + /** + * Get all relay adapters + * @returns {Array} + */ + getRelays () { + return this.nostrRelays + } + + /** + * Broadcast an event to all relays + * @param {Object} event - Event object to broadcast + * @returns {Promise} Array of results from each relay: { accepted, message, relayUrl } + */ + async broadcastEvent (event) { + const broadcastPromises = this.nostrRelays.map(async (relay) => { + try { + const result = await relay.sendEvent(event) + return { + accepted: result.accepted, + message: result.message || '', + relayUrl: relay.relayUrl, + success: true + } + } catch (err) { + return { + accepted: false, + message: err.message || 'Error broadcasting to relay', + relayUrl: relay.relayUrl, + success: false, + error: err + } + } + }) + + return Promise.allSettled(broadcastPromises).then(results => { + return results.map((result, index) => { + if (result.status === 'fulfilled') { + return result.value + } else { + return { + accepted: false, + message: result.reason?.message || 'Unknown error', + relayUrl: this.nostrRelays[index].relayUrl, + success: false, + error: result.reason + } + } + }) + }) + } + + /** + * Query all relays concurrently and merge results + * @param {Array} filters - Array of filter objects + * @param {string} subscriptionId - Unique subscription ID (will be modified per relay) + * @returns {Promise} Merged and de-duplicated array of events + */ + async queryAllRelays (filters, subscriptionId) { + // Create unique subscription IDs for each relay + const subscriptionIds = this.nostrRelays.map((relay, index) => + `${subscriptionId}-relay-${index}` + ) + + // Collect events from all relays + const allEvents = [] + const relayStatuses = this.nostrRelays.map(() => ({ + eoseReceived: false, + closedReceived: false, + closedMessage: '' + })) + + // Query all relays concurrently + const queryPromises = this.nostrRelays.map(async (relay, index) => { + const subscriptionIdForRelay = subscriptionIds[index] + const status = relayStatuses[index] + + // Create handlers for this relay + const handlers = { + onEvent: (event) => { + allEvents.push(event) + }, + onEose: () => { + status.eoseReceived = true + }, + onClosed: (message) => { + status.closedReceived = true + status.closedMessage = message + } + } + + try { + await relay.sendReq(subscriptionIdForRelay, filters, handlers) + + // Wait for EOSE or CLOSED with timeout + const timeout = 30000 // 30 seconds + const startTime = Date.now() + + while (!status.eoseReceived && !status.closedReceived && (Date.now() - startTime) < timeout) { + await new Promise(resolve => setTimeout(resolve, 100)) + } + + // Clean up subscription - always try to close, even if EOSE didn't come + try { + await relay.sendClose(subscriptionIdForRelay) + } catch (err) { + // Ignore close errors + } + + if (status.closedReceived) { + throw new Error(`Subscription closed on ${relay.relayUrl}: ${status.closedMessage}`) + } + + // If EOSE never came but timeout expired, that's OK - we proceed with what we got + if (!status.eoseReceived && (Date.now() - startTime) >= timeout) { + // Timeout reached without EOSE - proceed anyway with events collected so far + } + } catch (err) { + // Ensure cleanup even on error + try { + await relay.sendClose(subscriptionIdForRelay) + } catch (closeErr) { + // Ignore close errors + } + // Log error but don't fail the entire query + console.warn(`Query failed for relay ${relay.relayUrl}:`, err.message) + } + }) + + // Wait for all queries to complete + await Promise.allSettled(queryPromises) + + // Merge and de-duplicate events by event ID + const eventMap = new Map() + allEvents.forEach(event => { + if (event && event.id && !eventMap.has(event.id)) { + eventMap.set(event.id, event) + } + }) + + return Array.from(eventMap.values()) + } +} + +export default Adapters diff --git a/src/adapters/slp-indexer-api.js b/src/adapters/slp-indexer-api.js new file mode 100644 index 0000000..37fb721 --- /dev/null +++ b/src/adapters/slp-indexer-api.js @@ -0,0 +1,124 @@ +/* + Adapter library for interacting with SLP Indexer API service over HTTP. +*/ + +import axios from 'axios' +import wlogger from './wlogger.js' +import config from '../config/index.js' + +class SlpIndexerAPIAdapter { + constructor (localConfig = {}) { + this.config = localConfig.config || config + + // Allow missing config for testing environments + if (!this.config.slpIndexerApi || !this.config.slpIndexerApi.baseUrl) { + if (process.env.NODE_ENV === 'test' || process.env.TEST) { + // In test environment, create a mock baseURL + this.config.slpIndexerApi = { + baseUrl: 'http://localhost:5021', + timeoutMs: 15000 + } + } else { + throw new Error('SLP_INDEXER_API env var not set. Can not connect to PSF SLP indexer.') + } + } + + const { + baseUrl, + timeoutMs = 15000 + } = this.config.slpIndexerApi + + this.http = axios.create({ + baseURL: baseUrl, + timeout: timeoutMs + }) + } + + async get (path) { + try { + const response = await this.http.get(path) + return response.data + } catch (err) { + throw this._handleError(err) + } + } + + async post (path, data) { + try { + const response = await this.http.post(path, data) + return response.data + } catch (err) { + throw this._handleError(err) + } + } + + _handleError (err) { + const { status, message } = this.decodeError(err) + const error = new Error(message) + error.status = status + error.originalError = err + return error + } + + decodeError (err) { + try { + // Attempt to extract error message from response data + if (err.response && err.response.data) { + const data = err.response.data + // Handle structured error responses + if (data.error) { + return this._formatError(data.error, err.response.status || 400) + } + // Handle string error messages + if (typeof data === 'string') { + return this._formatError(data, err.response.status || 400) + } + // Handle object responses that might contain error info + if (typeof data === 'object' && data.message) { + return this._formatError(data.message, err.response.status || 400) + } + // Fallback to returning the status + return this._formatError('SLP Indexer API error', err.response.status || 500) + } + + // Network errors + if (err.message) { + if (err.message.includes('ENOTFOUND') || err.message.includes('ENETUNREACH') || err.message.includes('EAI_AGAIN')) { + return this._formatError( + 'Network error: Could not communicate with SLP Indexer API service.', + 503 + ) + } + } + + if (err.code && (err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED')) { + return this._formatError( + 'Network error: Could not communicate with SLP Indexer API service.', + 503 + ) + } + + if (err.error && typeof err.error === 'string' && err.error.includes('429')) { + return this._formatError('429 Too Many Requests', 429) + } + + if (err.message) { + return this._formatError(err.message, err.status || 422) + } + + return this._formatError('Unhandled SLP Indexer API error', 500) + } catch (decodeError) { + wlogger.error('Unhandled error in SlpIndexerAPIAdapter.decodeError()', decodeError) + return this._formatError('Internal server error', 500) + } + } + + _formatError (message, status = 500) { + return { + message: message || 'Internal server error', + status: status || 500 + } + } +} + +export default SlpIndexerAPIAdapter diff --git a/src/adapters/wlogger.js b/src/adapters/wlogger.js new file mode 100644 index 0000000..bec8386 --- /dev/null +++ b/src/adapters/wlogger.js @@ -0,0 +1,79 @@ +/* + Instantiates and configures the Winston logging library. This utility library + can be called by other parts of the application to conveniently tap into the + logging library. +*/ + +// Global npm libraries +import winston from 'winston' +import 'winston-daily-rotate-file' + +// Local libraries +import config from '../config/index.js' + +// Hack to get __dirname back. +// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/ +import * as url from 'url' +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) + +let _this = null + +class Wlogger { + constructor (localConfig = {}) { + this.config = config + + // Configure daily-rotation transport. + this.transport = new winston.transports.DailyRotateFile({ + filename: `${__dirname.toString()}/../../logs/rest2nostr-${ + this.config.env + }-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + zippedArchive: false, + maxSize: '1m', // 1 megabyte + maxFiles: '5d', // 5 days + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ) + }) + + this.transport.on('rotate', this.notifyRotation) + + // This controls what goes into the log FILES + this.wlogger = winston.createLogger({ + level: this.config.logLevel || 'info', + format: winston.format.json(), + transports: [ + this.transport + ] + }) + + // Bind 'this' object to all methods + this.notifyRotation = this.notifyRotation.bind(this) + this.outputToConsole = this.outputToConsole.bind(this) + + _this = this + } + + notifyRotation (oldFilename, newFilename) { + _this.wlogger.info('Rotating log files') + } + + outputToConsole () { + this.wlogger.add( + new winston.transports.Console({ + format: winston.format.simple(), + level: this.config.logLevel || 'info' + }) + ) + } +} + +const logger = new Wlogger() + +// Allow the logger to write to the console. +logger.outputToConsole() + +const wlogger = logger.wlogger + +export { wlogger as default, Wlogger } diff --git a/src/config/env/common.js b/src/config/env/common.js new file mode 100644 index 0000000..f8e52d8 --- /dev/null +++ b/src/config/env/common.js @@ -0,0 +1,92 @@ +/* + This file is used to store unsecure, application-specific data common to all + environments. +*/ + +import dotenv from 'dotenv' + +// Hack to get __dirname back. +// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/ +import * as url from 'url' +import { readFileSync } from 'fs' +dotenv.config() + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) +const pkgInfo = JSON.parse(readFileSync(`${__dirname.toString()}/../../../package.json`)) + +const version = pkgInfo.version + +// This function is used to convert the string input of an environment variable to a boolean value. +const normalizeBoolean = (value, defaultValue) => { + if (value === undefined || value === null || value === '') return defaultValue + + const normalized = String(value).trim().toLowerCase() + if (['false', '0', 'no', 'off'].includes(normalized)) return false + if (['true', '1', 'yes', 'on'].includes(normalized)) return true + return defaultValue +} + +// By default, the price per API call is 200 satoshis. +// But the user can override this value by setting the X402_PRICE_SAT environment variable. +const parsedPriceSat = Number(process.env.X402_PRICE_SAT) +const priceSat = Number.isFinite(parsedPriceSat) && parsedPriceSat > 0 ? parsedPriceSat : 200 + +const x402Defaults = { + enabled: normalizeBoolean(process.env.X402_ENABLED, true), + facilitatorUrl: process.env.FACILITATOR_URL || 'http://localhost:4345/facilitator', + serverAddress: process.env.SERVER_BCH_ADDRESS || 'bitcoincash:qqsrke9lh257tqen99dkyy2emh4uty0vky9y0z0lsr', + priceSat +} + +const basicAuthDefaults = { + enabled: normalizeBoolean(process.env.USE_BASIC_AUTH, false), + token: process.env.BASIC_AUTH_TOKEN || '' +} + +export default { + // Server port + port: parseInt(process.env.PORT, 10) || 5942, + + // Environment + env: process.env.NODE_ENV || 'development', + + // API prefix for REST controllers + apiPrefix: process.env.API_PREFIX || '/v6', + + // Logging level + logLevel: process.env.LOG_LEVEL || 'info', + + // Full node RPC configuration + fullNode: { + rpcBaseUrl: process.env.RPC_BASEURL || 'http://127.0.0.1:8332', + rpcUsername: process.env.RPC_USERNAME || '', + rpcPassword: process.env.RPC_PASSWORD || '', + rpcTimeoutMs: Number(process.env.RPC_TIMEOUT_MS || 15000), + rpcRequestIdPrefix: process.env.RPC_REQUEST_ID_PREFIX || 'psf-bch-api' + }, + + // Fulcrum API configuration + fulcrumApi: { + baseUrl: process.env.FULCRUM_API || '', + timeoutMs: Number(process.env.FULCRUM_TIMEOUT_MS || 15000) + }, + + // SLP Indexer API configuration + slpIndexerApi: { + baseUrl: process.env.SLP_INDEXER_API || '', + timeoutMs: Number(process.env.SLP_INDEXER_TIMEOUT_MS || 15000) + }, + + // REST API URL for wallet operations + restURL: process.env.REST_URL || process.env.LOCAL_RESTURL || 'http://127.0.0.1:5942/v6/', + + // IPFS Gateway URL + ipfsGateway: process.env.IPFS_GATEWAY || 'p2wdb-gateway-678.fullstack.cash', + + x402: x402Defaults, + + basicAuth: basicAuthDefaults, + + // Version + version +} diff --git a/src/config/env/development.js b/src/config/env/development.js new file mode 100644 index 0000000..aa7db30 --- /dev/null +++ b/src/config/env/development.js @@ -0,0 +1,7 @@ +/* + These are the environment settings for the DEVELOPMENT environment. +*/ + +export default { + env: 'development' +} diff --git a/src/config/env/production.js b/src/config/env/production.js new file mode 100644 index 0000000..70158ee --- /dev/null +++ b/src/config/env/production.js @@ -0,0 +1,7 @@ +/* + These are the environment settings for the PRODUCTION environment. +*/ + +export default { + env: 'production' +} diff --git a/src/config/index.js b/src/config/index.js new file mode 100644 index 0000000..0fdf1b4 --- /dev/null +++ b/src/config/index.js @@ -0,0 +1,14 @@ +import common from './env/common.js' + +import development from './env/development.js' +import production from './env/production.js' + +const env = process.env.NODE_ENV || 'development' +console.log(`Loading config for this environment: ${env}`) + +let config = development +if (env === 'production') { + config = production +} + +export default Object.assign({}, common, config) diff --git a/src/config/x402.js b/src/config/x402.js new file mode 100644 index 0000000..93099e3 --- /dev/null +++ b/src/config/x402.js @@ -0,0 +1,50 @@ +import config from './index.js' + +const DEFAULT_DESCRIPTION = 'Access to protected psf-bch-api resources' +const DEFAULT_TIMEOUT_SECONDS = 60 +const NETWORK = 'bch' + +/** + * Builds a route configuration map for x402-bch middleware. + * + * @param {string} apiPrefix Express API prefix (e.g., "/v6") + * @returns {Object} Routes configuration compatible with x402-bch-express + */ +export function buildX402Routes (apiPrefix = '/v6') { + const normalizedPrefix = apiPrefix.endsWith('/') + ? apiPrefix.slice(0, -1) + : apiPrefix + const prefixWithSlash = normalizedPrefix.startsWith('/') + ? normalizedPrefix + : `/${normalizedPrefix}` + + const routeKey = `${prefixWithSlash}/*` + + return { + network: NETWORK, + [routeKey]: { + price: config.x402.priceSat, + network: NETWORK, + config: { + description: `${DEFAULT_DESCRIPTION} (${config.x402.priceSat} satoshis)`, + maxTimeoutSeconds: DEFAULT_TIMEOUT_SECONDS + } + } + } +} + +export function getX402Settings () { + return { + enabled: Boolean(config.x402?.enabled), + facilitatorUrl: config.x402?.facilitatorUrl, + serverAddress: config.x402?.serverAddress, + priceSat: config.x402?.priceSat + } +} + +export function getBasicAuthSettings () { + return { + enabled: Boolean(config.basicAuth?.enabled), + token: config.basicAuth?.token || '' + } +} diff --git a/src/controllers/index.js b/src/controllers/index.js new file mode 100644 index 0000000..aa11e12 --- /dev/null +++ b/src/controllers/index.js @@ -0,0 +1,58 @@ +/* + This is a top-level library that encapsulates all the additional Controllers. + The concept of Controllers comes from Clean Architecture: + https://troutsblog.com/blog/clean-architecture +*/ + +// Local libraries +import Adapters from '../adapters/index.js' +import UseCases from '../use-cases/index.js' +import RESTControllers from './rest-api/index.js' +import TimerController from './timer-controller.js' +import config from '../config/index.js' + +class Controllers { + constructor (localConfig = {}) { + // Encapsulate dependencies + this.adapters = new Adapters(localConfig) + this.useCases = new UseCases({ adapters: this.adapters }) + this.config = config + this.timerController = new TimerController({ adapters: this.adapters, useCases: this.useCases }) + this.apiPrefix = this.config.apiPrefix || '/v6' + + // Bind 'this' object to all subfunctions + this.initAdapters = this.initAdapters.bind(this) + this.initUseCases = this.initUseCases.bind(this) + this.attachRESTControllers = this.attachRESTControllers.bind(this) + } + + // Spin up any adapter libraries that have async startup needs. + async initAdapters () { + await this.adapters.start() + } + + // Run any Use Cases to startup the app. + async initUseCases () { + await this.useCases.start() + } + + // Initialize all the controllers. + async initControllers () { + this.timerController.startTimerControllers() + } + + // Top-level function for this library. + // Start the various Controllers and attach them to the app. + attachRESTControllers (app) { + const restControllers = new RESTControllers({ + adapters: this.adapters, + useCases: this.useCases, + apiPrefix: this.apiPrefix + }) + + // Attach the REST API Controllers to the Express app. + restControllers.attachRESTControllers(app) + } +} + +export default Controllers diff --git a/src/controllers/rest-api/encryption/controller.js b/src/controllers/rest-api/encryption/controller.js new file mode 100644 index 0000000..aa9c068 --- /dev/null +++ b/src/controllers/rest-api/encryption/controller.js @@ -0,0 +1,100 @@ +/* + REST API Controller for the /encryption routes. +*/ + +import wlogger from '../../../adapters/wlogger.js' + +class EncryptionRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Encryption REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.encryption) { + throw new Error( + 'Instance of Encryption use cases required when instantiating Encryption REST Controller.' + ) + } + + this.encryptionUseCases = this.useCases.encryption + + // Bind functions + this.root = this.root.bind(this) + this.getPublicKey = this.getPublicKey.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/encryption/ Service status + * @apiName EncryptionRoot + * @apiGroup Encryption + * + * @apiDescription Returns the status of the encryption service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'encryption' }) + } + + /** + * @api {get} /v6/encryption/publickey/:address Get public key for a BCH address + * @apiName GetPublicKey + * @apiGroup Encryption + * @apiDescription Searches the blockchain for a public key associated with a + * BCH address. Returns an object. If successful, the publicKey property will + * contain a hexadecimal representation of the public key. + * + * @apiParam {String} address BCH address (cash address or legacy format) + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json" + * + * @apiSuccess {Boolean} success Indicates if the operation was successful + * @apiSuccess {String} publicKey The public key in hexadecimal format, or "not found" + */ + async getPublicKey (req, res) { + try { + const address = req.params.address + + // Reject if address is an array + if (Array.isArray(address)) { + res.status(400) + return res.json({ + success: false, + error: 'address can not be an array.' + }) + } + + // Reject if address is missing + if (!address) { + res.status(400) + return res.json({ + success: false, + error: 'address is required.' + }) + } + + const result = await this.encryptionUseCases.getPublicKey({ address }) + + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in EncryptionRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ success: false, error: message }) + } +} + +export default EncryptionRESTController diff --git a/src/controllers/rest-api/encryption/router.js b/src/controllers/rest-api/encryption/router.js new file mode 100644 index 0000000..fb8e89e --- /dev/null +++ b/src/controllers/rest-api/encryption/router.js @@ -0,0 +1,51 @@ +/* + REST API router for /encryption routes. +*/ + +import express from 'express' +import EncryptionRESTController from './controller.js' + +class EncryptionRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Encryption REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating Encryption REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.encryptionController = new EncryptionRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/encryption` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.encryptionController.root) + this.router.get('/publickey/:address', this.encryptionController.getPublicKey) + + app.use(this.baseUrl, this.router) + } +} + +export default EncryptionRouter diff --git a/src/controllers/rest-api/fulcrum/controller.js b/src/controllers/rest-api/fulcrum/controller.js new file mode 100644 index 0000000..ca018df --- /dev/null +++ b/src/controllers/rest-api/fulcrum/controller.js @@ -0,0 +1,564 @@ +/* + REST API Controller for the /fulcrum routes. +*/ + +import wlogger from '../../../adapters/wlogger.js' +import BCHJS from '@psf/bch-js' +import config from '../../../config/index.js' + +const bchjs = new BCHJS({ restURL: config.restURL }) + +class FulcrumRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Fulcrum REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.fulcrum) { + throw new Error( + 'Instance of Fulcrum use cases required when instantiating Fulcrum REST Controller.' + ) + } + + this.fulcrumUseCases = this.useCases.fulcrum + + // Bind functions + this.root = this.root.bind(this) + this.getBalance = this.getBalance.bind(this) + this.balanceBulk = this.balanceBulk.bind(this) + this.getUtxos = this.getUtxos.bind(this) + this.utxosBulk = this.utxosBulk.bind(this) + this.getTransactionDetails = this.getTransactionDetails.bind(this) + this.transactionDetailsBulk = this.transactionDetailsBulk.bind(this) + this.broadcastTransaction = this.broadcastTransaction.bind(this) + this.getBlockHeaders = this.getBlockHeaders.bind(this) + this.blockHeadersBulk = this.blockHeadersBulk.bind(this) + this.getTransactions = this.getTransactions.bind(this) + this.transactionsBulk = this.transactionsBulk.bind(this) + this.getMempool = this.getMempool.bind(this) + this.mempoolBulk = this.mempoolBulk.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/fulcrum/ Service status + * @apiName FulcrumRoot + * @apiGroup Fulcrum + * + * @apiDescription Returns the status of the fulcrum service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'fulcrum' }) + } + + /** + * Validates and converts an address to cash address format + * @param {string} address - Address to validate and convert + * @returns {string} Cash address + * @throws {Error} If address is invalid or not mainnet + */ + _validateAndConvertAddress (address) { + if (!address) { + throw new Error('address is empty') + } + + // Convert legacy to cash address + const cashAddr = bchjs.Address.toCashAddress(address) + + // Ensure it's a valid BCH address + try { + bchjs.Address.toLegacyAddress(cashAddr) + } catch (err) { + throw new Error(`Invalid BCH address. Double check your address is valid: ${address}`) + } + + // Ensure it's mainnet (no testnet support) + const isMainnet = bchjs.Address.isMainnetAddress(cashAddr) + if (!isMainnet) { + throw new Error('Invalid network. Only mainnet addresses are supported.') + } + + return cashAddr + } + + /** + * @api {get} /v6/fulcrum/balance/:address Get balance for a single address + * @apiName GetBalance + * @apiGroup Fulcrum + * @apiDescription Returns an object with confirmed and unconfirmed balance associated with an address. + */ + async getBalance (req, res) { + try { + const address = req.params.address + + if (Array.isArray(address)) { + return res.status(400).json({ + success: false, + error: 'address can not be an array. Use POST for bulk upload.' + }) + } + + const cashAddr = this._validateAndConvertAddress(address) + + const result = await this.fulcrumUseCases.getBalance({ address: cashAddr }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/fulcrum/balance Get balances for an array of addresses + * @apiName GetBalances + * @apiGroup Fulcrum + * @apiDescription Returns an array of balances associated with an array of addresses. Limited to 20 items per request. + */ + async balanceBulk (req, res) { + try { + const addresses = req.body.addresses + + if (!Array.isArray(addresses)) { + return res.status(400).json({ + success: false, + error: 'addresses needs to be an array. Use GET for single address.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(addresses.length)) { + return res.status(400).json({ + success: false, + error: 'Array too large.' + }) + } + + // Validate and convert all addresses + const validatedAddresses = [] + for (let i = 0; i < addresses.length; i++) { + try { + const cashAddr = this._validateAndConvertAddress(addresses[i]) + validatedAddresses.push(cashAddr) + } catch (err) { + return res.status(400).json({ + success: false, + error: err.message + }) + } + } + + const result = await this.fulcrumUseCases.getBalances({ addresses: validatedAddresses }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/fulcrum/utxos/:address Get utxos for a single address + * @apiName GetUtxos + * @apiGroup Fulcrum + * @apiDescription Returns an object with UTXOs associated with an address. + */ + async getUtxos (req, res) { + try { + const address = req.params.address + + if (Array.isArray(address)) { + return res.status(400).json({ + success: false, + error: 'address can not be an array. Use POST for bulk upload.' + }) + } + + const cashAddr = this._validateAndConvertAddress(address) + + const result = await this.fulcrumUseCases.getUtxos({ address: cashAddr }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/fulcrum/utxos Get utxos for an array of addresses + * @apiName GetUtxosBulk + * @apiGroup Fulcrum + * @apiDescription Returns an array of objects with UTXOs associated with an address. Limited to 20 items per request. + */ + async utxosBulk (req, res) { + try { + const addresses = req.body.addresses + + if (!Array.isArray(addresses)) { + return res.status(400).json({ + success: false, + error: 'addresses needs to be an array. Use GET for single address.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(addresses.length)) { + return res.status(400).json({ + success: false, + error: 'Array too large.' + }) + } + + // Validate and convert all addresses + const validatedAddresses = [] + for (let i = 0; i < addresses.length; i++) { + try { + const cashAddr = this._validateAndConvertAddress(addresses[i]) + validatedAddresses.push(cashAddr) + } catch (err) { + return res.status(400).json({ + success: false, + error: err.message + }) + } + } + + const result = await this.fulcrumUseCases.getUtxosBulk({ addresses: validatedAddresses }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/fulcrum/tx/data/:txid Get transaction details for a TXID + * @apiName GetTransactionDetails + * @apiGroup Fulcrum + * @apiDescription Returns an object with transaction details of the TXID + */ + async getTransactionDetails (req, res) { + try { + const txid = req.params.txid + + if (typeof txid !== 'string') { + return res.status(400).json({ + success: false, + error: 'txid must be a string' + }) + } + + const result = await this.fulcrumUseCases.getTransactionDetails({ txid }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/fulcrum/tx/data Get transaction details for an array of TXIDs + * @apiName GetTransactionDetailsBulk + * @apiGroup Fulcrum + * @apiDescription Returns an array of objects with transaction details of an array of TXIDs. Limited to 20 items per request. + */ + async transactionDetailsBulk (req, res) { + try { + const txids = req.body.txids + const verbose = req.body.verbose !== undefined ? req.body.verbose : true + + if (!Array.isArray(txids)) { + return res.status(400).json({ + success: false, + error: 'txids needs to be an array. Use GET for single txid.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(txids.length)) { + return res.status(400).json({ + success: false, + error: 'Array too large.' + }) + } + + const result = await this.fulcrumUseCases.getTransactionDetailsBulk({ txids, verbose }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/fulcrum/tx/broadcast Broadcast a raw transaction + * @apiName BroadcastTransaction + * @apiGroup Fulcrum + * @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure. + */ + async broadcastTransaction (req, res) { + try { + const txHex = req.body.txHex + + if (typeof txHex !== 'string') { + return res.status(400).json({ + success: false, + error: 'txHex must be a string' + }) + } + + const result = await this.fulcrumUseCases.broadcastTransaction({ txHex }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/fulcrum/block/headers/:height Get block headers + * @apiName GetBlockHeaders + * @apiGroup Fulcrum + * @apiDescription Returns an array with block headers starting at the block height + * + * @apiParam {Number} height Block height + * @apiParam {Number} count Number of block headers to return (query parameter, default: 1) + */ + async getBlockHeaders (req, res) { + try { + const heightRaw = req.params.height + const countRaw = req.query.count + + const height = Number(heightRaw) + const count = countRaw === undefined ? 1 : Number(countRaw) + + if (Number.isNaN(height) || height < 0) { + return res.status(400).json({ + success: false, + error: 'height must be a positive number' + }) + } + + if (Number.isNaN(count) || count < 0) { + return res.status(400).json({ + success: false, + error: 'count must be a positive number' + }) + } + + const result = await this.fulcrumUseCases.getBlockHeaders({ height, count }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/fulcrum/block/headers Get block headers for an array of height + count pairs + * @apiName GetBlockHeadersBulk + * @apiGroup Fulcrum + * @apiDescription Returns an array of objects with block headers. Limited to 20 items per request. + */ + async blockHeadersBulk (req, res) { + try { + const heights = req.body.heights + + if (!Array.isArray(heights)) { + return res.status(400).json({ + success: false, + error: 'heights needs to be an array. Use GET for single height.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(heights.length)) { + return res.status(400).json({ + success: false, + error: 'Array too large.' + }) + } + + // Validate each height object + for (const item of heights) { + if (!item || typeof item.height !== 'number' || typeof item.count !== 'number') { + return res.status(400).json({ + success: false, + error: 'Each height object must have numeric height and count properties' + }) + } + if (item.height < 0 || item.count < 0) { + return res.status(400).json({ + success: false, + error: 'height and count must be positive numbers' + }) + } + } + + const result = await this.fulcrumUseCases.getBlockHeadersBulk({ heights }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/fulcrum/transactions/:address Get transaction history for a single address + * @apiName GetTransactions + * @apiGroup Fulcrum + * @apiDescription Returns an array of historical transactions associated with an address. Results are returned in descending order (most recent TX first). Passing allTxs=true will return the entire transaction history, otherwise, only the last 100 TXIDs will be returned. + * + * @apiParam {String} address Address + * @apiParam {Boolean} allTxs Optional: return all transactions (default: false, limited to 100) + */ + async getTransactions (req, res) { + try { + const address = req.params.address + let allTxs = false + + // Check if allTxs is in params or query + if (req.params.allTxs) { + allTxs = req.params.allTxs === 'true' + } else if (req.query.allTxs) { + allTxs = req.query.allTxs === 'true' + } + + if (Array.isArray(address)) { + return res.status(400).json({ + success: false, + error: 'address can not be an array. Use POST for bulk upload.' + }) + } + + const cashAddr = this._validateAndConvertAddress(address) + + const result = await this.fulcrumUseCases.getTransactions({ address: cashAddr, allTxs }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/fulcrum/transactions Get the transaction history for an array of addresses + * @apiName GetTransactionsBulk + * @apiGroup Fulcrum + * @apiDescription Returns an array of transactions associated with an array of addresses. Limited to 20 items per request. Passing allTxs=true will return the entire transaction history, otherwise, only the last 100 TXIDs will be returned. + */ + async transactionsBulk (req, res) { + try { + const addresses = req.body.addresses + const allTxs = req.body.allTxs === true + + if (!Array.isArray(addresses)) { + return res.status(400).json({ + success: false, + error: 'addresses needs to be an array. Use GET for single address.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(addresses.length)) { + return res.status(400).json({ + success: false, + error: 'Array too large.' + }) + } + + // Validate and convert all addresses + const validatedAddresses = [] + for (let i = 0; i < addresses.length; i++) { + try { + const cashAddr = this._validateAndConvertAddress(addresses[i]) + validatedAddresses.push(cashAddr) + } catch (err) { + return res.status(400).json({ + success: false, + error: err.message + }) + } + } + + const result = await this.fulcrumUseCases.getTransactionsBulk({ + addresses: validatedAddresses, + allTxs + }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/fulcrum/unconfirmed/:address Get unconfirmed utxos for a single address + * @apiName GetMempool + * @apiGroup Fulcrum + * @apiDescription Returns an object with unconfirmed UTXOs associated with an address. + */ + async getMempool (req, res) { + try { + const address = req.params.address + + if (Array.isArray(address)) { + return res.status(400).json({ + success: false, + error: 'address can not be an array. Use POST for bulk upload.' + }) + } + + const cashAddr = this._validateAndConvertAddress(address) + + const result = await this.fulcrumUseCases.getMempool({ address: cashAddr }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/fulcrum/unconfirmed Get unconfirmed utxos for an array of addresses + * @apiName GetMempoolBulk + * @apiGroup Fulcrum + * @apiDescription Returns an array of objects with unconfirmed UTXOs associated with an address. Limited to 20 items per request. + */ + async mempoolBulk (req, res) { + try { + const addresses = req.body.addresses + + if (!Array.isArray(addresses)) { + return res.status(400).json({ + success: false, + error: 'addresses needs to be an array. Use GET for single address.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(addresses.length)) { + return res.status(400).json({ + success: false, + error: 'Array too large.' + }) + } + + // Validate and convert all addresses + const validatedAddresses = [] + for (let i = 0; i < addresses.length; i++) { + try { + const cashAddr = this._validateAndConvertAddress(addresses[i]) + validatedAddresses.push(cashAddr) + } catch (err) { + return res.status(400).json({ + success: false, + error: err.message + }) + } + } + + const result = await this.fulcrumUseCases.getMempoolBulk({ addresses: validatedAddresses }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in FulcrumRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ error: message }) + } +} + +export default FulcrumRESTController diff --git a/src/controllers/rest-api/fulcrum/router.js b/src/controllers/rest-api/fulcrum/router.js new file mode 100644 index 0000000..970576b --- /dev/null +++ b/src/controllers/rest-api/fulcrum/router.js @@ -0,0 +1,64 @@ +/* + REST API router for /full-node/fulcrum routes. +*/ + +import express from 'express' +import FulcrumRESTController from './controller.js' + +class FulcrumRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Fulcrum REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating Fulcrum REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.fulcrumController = new FulcrumRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/fulcrum` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.fulcrumController.root) + this.router.get('/balance/:address', this.fulcrumController.getBalance) + this.router.post('/balance', this.fulcrumController.balanceBulk) + this.router.get('/utxos/:address', this.fulcrumController.getUtxos) + this.router.post('/utxos', this.fulcrumController.utxosBulk) + this.router.get('/tx/data/:txid', this.fulcrumController.getTransactionDetails) + this.router.post('/tx/data', this.fulcrumController.transactionDetailsBulk) + this.router.post('/tx/broadcast', this.fulcrumController.broadcastTransaction) + this.router.get('/block/headers/:height', this.fulcrumController.getBlockHeaders) + this.router.post('/block/headers', this.fulcrumController.blockHeadersBulk) + this.router.get('/transactions/:address', this.fulcrumController.getTransactions) + this.router.get('/transactions/:address/:allTxs', this.fulcrumController.getTransactions) + this.router.post('/transactions', this.fulcrumController.transactionsBulk) + this.router.get('/unconfirmed/:address', this.fulcrumController.getMempool) + this.router.post('/unconfirmed', this.fulcrumController.mempoolBulk) + + app.use(this.baseUrl, this.router) + } +} + +export default FulcrumRouter diff --git a/src/controllers/rest-api/full-node/blockchain/controller.js b/src/controllers/rest-api/full-node/blockchain/controller.js new file mode 100644 index 0000000..78080d5 --- /dev/null +++ b/src/controllers/rest-api/full-node/blockchain/controller.js @@ -0,0 +1,553 @@ +/* + REST API Controller for the /full-node/blockchain routes. +*/ + +import wlogger from '../../../../adapters/wlogger.js' + +class BlockchainRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Blockchain REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.blockchain) { + throw new Error( + 'Instance of Blockchain use cases required when instantiating Blockchain REST Controller.' + ) + } + + this.blockchainUseCases = this.useCases.blockchain + + // Bind functions + this.root = this.root.bind(this) + this.getBestBlockHash = this.getBestBlockHash.bind(this) + this.getBlockchainInfo = this.getBlockchainInfo.bind(this) + this.getBlockCount = this.getBlockCount.bind(this) + this.getBlockHeaderSingle = this.getBlockHeaderSingle.bind(this) + this.getBlockHeaderBulk = this.getBlockHeaderBulk.bind(this) + this.getChainTips = this.getChainTips.bind(this) + this.getDifficulty = this.getDifficulty.bind(this) + this.getMempoolEntrySingle = this.getMempoolEntrySingle.bind(this) + this.getMempoolEntryBulk = this.getMempoolEntryBulk.bind(this) + this.getMempoolAncestorsSingle = this.getMempoolAncestorsSingle.bind(this) + this.getMempoolInfo = this.getMempoolInfo.bind(this) + this.getRawMempool = this.getRawMempool.bind(this) + this.getTxOut = this.getTxOut.bind(this) + this.getTxOutPost = this.getTxOutPost.bind(this) + this.getTxOutProofSingle = this.getTxOutProofSingle.bind(this) + this.getTxOutProofBulk = this.getTxOutProofBulk.bind(this) + this.verifyTxOutProofSingle = this.verifyTxOutProofSingle.bind(this) + this.verifyTxOutProofBulk = this.verifyTxOutProofBulk.bind(this) + this.getBlock = this.getBlock.bind(this) + this.getBlockHash = this.getBlockHash.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/full-node/blockchain/ Service status + * @apiName BlockchainRoot + * @apiGroup Blockchain + * + * @apiDescription Returns the status of the blockchain service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'blockchain' }) + } + + /** + * @api {get} /v6/full-node/blockchain/getBestBlockHash Get best block hash + * @apiName GetBestBlockHash + * @apiGroup Blockchain + * @apiDescription Returns the hash of the best (tip) block in the longest block chain. + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/full-node/blockchain/getBestBlockHash" -H "accept: application/json" + * + * @apiSuccess {String} bestBlockHash Hash of the best block + */ + async getBestBlockHash (req, res) { + try { + const result = await this.blockchainUseCases.getBestBlockHash() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getBlockchainInfo Get blockchain info + * @apiName GetBlockchainInfo + * @apiGroup Blockchain + * @apiDescription Returns various state info regarding blockchain processing. + */ + async getBlockchainInfo (req, res) { + try { + const result = await this.blockchainUseCases.getBlockchainInfo() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getBlockCount Get block count + * @apiName GetBlockCount + * @apiGroup Blockchain + * @apiDescription Returns the number of blocks in the longest blockchain. + */ + async getBlockCount (req, res) { + try { + const result = await this.blockchainUseCases.getBlockCount() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getBlockHeader/:hash Get single block header + * @apiName GetSingleBlockHeader + * @apiGroup Blockchain + * @apiDescription Returns serialized block header data. + * + * @apiParam {String} hash Block hash + * @apiParam {Boolean} verbose Return verbose data (default false) + */ + async getBlockHeaderSingle (req, res) { + try { + const hash = req.params.hash + if (!hash) { + return res.status(400).json({ error: 'hash can not be empty' }) + } + + const verbose = req.query.verbose?.toString() === 'true' + const result = await this.blockchainUseCases.getBlockHeader({ hash, verbose }) + + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/blockchain/getBlockHeader Get multiple block headers + * @apiName GetBulkBlockHeader + * @apiGroup Blockchain + * @apiDescription Returns serialized block header data for multiple hashes. + * + * @apiParam {String[]} hashes Block hashes + * @apiParam {Boolean} verbose Return verbose data (default false) + */ + async getBlockHeaderBulk (req, res) { + try { + const hashes = req.body.hashes + const verbose = !!req.body.verbose + + if (!Array.isArray(hashes)) { + return res.status(400).json({ + error: 'hashes needs to be an array. Use GET for single hash.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(hashes.length)) { + return res.status(400).json({ error: 'Array too large.' }) + } + + for (const hash of hashes) { + if (!hash || hash.length !== 64) { + return res.status(400).json({ error: `This is not a hash: ${hash}` }) + } + } + + const result = await this.blockchainUseCases.getBlockHeaders({ hashes, verbose }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getChainTips Get chain tips + * @apiName GetChainTips + * @apiGroup Blockchain + * @apiDescription Returns information about known tips in the block tree. + */ + async getChainTips (req, res) { + try { + const result = await this.blockchainUseCases.getChainTips() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getDifficulty Get difficulty + * @apiName GetDifficulty + * @apiGroup Blockchain + * @apiDescription Returns the current difficulty value. + */ + async getDifficulty (req, res) { + try { + const result = await this.blockchainUseCases.getDifficulty() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getMempoolEntry/:txid Get single mempool entry + * @apiName GetMempoolEntry + * @apiGroup Blockchain + * @apiDescription Returns mempool data for a transaction. + */ + async getMempoolEntrySingle (req, res) { + try { + const txid = req.params.txid + if (!txid) { + return res.status(400).json({ error: 'txid can not be empty' }) + } + + const result = await this.blockchainUseCases.getMempoolEntry({ txid }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/blockchain/getMempoolEntry Get bulk mempool entry + * @apiName GetMempoolEntryBulk + * @apiGroup Blockchain + * @apiDescription Returns mempool data for multiple transactions. + */ + async getMempoolEntryBulk (req, res) { + try { + const txids = req.body.txids + + if (!Array.isArray(txids)) { + return res.status(400).json({ + error: 'txids needs to be an array. Use GET for single txid.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(txids.length)) { + return res.status(400).json({ error: 'Array too large.' }) + } + + for (const txid of txids) { + if (!txid || txid.length !== 64) { + return res.status(400).json({ error: 'This is not a txid' }) + } + } + + const result = await this.blockchainUseCases.getMempoolEntries({ txids }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getMempoolAncestors/:txid Get mempool ancestors + * @apiName GetMempoolAncestors + * @apiGroup Blockchain + * @apiDescription Returns mempool ancestor data for a transaction. + */ + async getMempoolAncestorsSingle (req, res) { + try { + const txid = req.params.txid + if (!txid) { + return res.status(400).json({ error: 'txid can not be empty' }) + } + + let verbose = false + if (req.query.verbose && req.query.verbose.toString() === 'true') { + verbose = true + } + + const result = await this.blockchainUseCases.getMempoolAncestors({ txid, verbose }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getMempoolInfo Get mempool info + * @apiName GetMempoolInfo + * @apiGroup Blockchain + * @apiDescription Returns details on the state of the mempool. + */ + async getMempoolInfo (req, res) { + try { + const result = await this.blockchainUseCases.getMempoolInfo() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getRawMempool Get raw mempool + * @apiName GetRawMempool + * @apiGroup Blockchain + * @apiDescription Returns all transaction ids in the mempool. + * + * @apiParam {Boolean} verbose Return verbose data (default false) + */ + async getRawMempool (req, res) { + try { + const verbose = req.query.verbose === 'true' + const result = await this.blockchainUseCases.getRawMempool({ verbose }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getTxOut/:txid/:n Get transaction output + * @apiName GetTxOut + * @apiGroup Blockchain + * @apiDescription Returns details about an unspent transaction output. + */ + async getTxOut (req, res) { + try { + const txid = req.params.txid + if (!txid) { + return res.status(400).json({ error: 'txid can not be empty' }) + } + + const nRaw = req.params.n + if (nRaw === undefined || nRaw === '') { + return res.status(400).json({ error: 'n can not be empty' }) + } + + const n = parseInt(nRaw) + const includeMempool = req.query.includeMempool === 'true' + + const result = await this.blockchainUseCases.getTxOut({ + txid, + n, + includeMempool + }) + + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/blockchain/getTxOut Validate a UTXO + * @apiName GetTxOutPost + * @apiGroup Blockchain + * @apiDescription Returns details about an unspent transaction output. + */ + async getTxOutPost (req, res) { + try { + const txid = req.body.txid + if (!txid) { + return res.status(400).json({ error: 'txid can not be empty' }) + } + + const voutRaw = req.body.vout + if (voutRaw === undefined || voutRaw === '') { + return res.status(400).json({ error: 'vout can not be empty' }) + } + + const n = parseInt(voutRaw) + const mempool = req.body.mempool !== undefined ? !!req.body.mempool : true + + const result = await this.blockchainUseCases.getTxOut({ + txid, + n, + includeMempool: mempool + }) + + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getTxOutProof/:txid Get TxOut proof + * @apiName GetTxOutProofSingle + * @apiGroup Blockchain + * @apiDescription Returns a hex-encoded proof that the transaction was included in a block. + */ + async getTxOutProofSingle (req, res) { + try { + const txid = req.params.txid + if (!txid) { + return res.status(400).json({ error: 'txid can not be empty' }) + } + + const result = await this.blockchainUseCases.getTxOutProof({ txid }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/blockchain/getTxOutProof Get TxOut proofs + * @apiName GetTxOutProofBulk + * @apiGroup Blockchain + * @apiDescription Returns hex-encoded proofs for transactions. + */ + async getTxOutProofBulk (req, res) { + try { + const txids = req.body.txids + + if (!Array.isArray(txids)) { + return res.status(400).json({ + error: 'txids needs to be an array. Use GET for single txid.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(txids.length)) { + return res.status(400).json({ error: 'Array too large.' }) + } + + for (const txid of txids) { + if (!txid || txid.length !== 64) { + return res.status(400).json({ + error: `Invalid txid. Double check your txid is valid: ${txid}` + }) + } + } + + const result = await this.blockchainUseCases.getTxOutProofs({ txids }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/verifyTxOutProof/:proof Verify TxOut proof + * @apiName VerifyTxOutProofSingle + * @apiGroup Blockchain + * @apiDescription Verifies a hex-encoded proof was included in a block. + */ + async verifyTxOutProofSingle (req, res) { + try { + const proof = req.params.proof + if (!proof) { + return res.status(400).json({ error: 'proof can not be empty' }) + } + + const result = await this.blockchainUseCases.verifyTxOutProof({ proof }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/blockchain/verifyTxOutProof Verify TxOut proofs + * @apiName VerifyTxOutProofBulk + * @apiGroup Blockchain + * @apiDescription Verifies hex-encoded proofs were included in blocks. + */ + async verifyTxOutProofBulk (req, res) { + try { + const proofs = req.body.proofs + + if (!Array.isArray(proofs)) { + return res.status(400).json({ + error: 'proofs needs to be an array. Use GET for single proof.' + }) + } + + if (!this.adapters.fullNode.validateArraySize(proofs.length)) { + return res.status(400).json({ error: 'Array too large.' }) + } + + for (const proof of proofs) { + if (!proof) { + return res.status(400).json({ error: `proof can not be empty: ${proof}` }) + } + } + + const result = await this.blockchainUseCases.verifyTxOutProofs({ proofs }) + const flattened = result.map(entry => Array.isArray(entry) ? entry[0] : entry) + + return res.status(200).json(flattened) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/blockchain/getBlock Get block details + * @apiName GetBlock + * @apiGroup Blockchain + * @apiDescription Returns block details for a hash. + */ + async getBlock (req, res) { + try { + const blockhash = req.body.blockhash + if (!blockhash) { + return res.status(400).json({ error: 'blockhash can not be empty' }) + } + + let verbosity = req.body.verbosity + if (verbosity === undefined || verbosity === null) { + verbosity = 1 + } + + const result = await this.blockchainUseCases.getBlock({ + blockhash, + verbosity + }) + + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/blockchain/getBlockHash/:height Get block hash + * @apiName GetBlockHash + * @apiGroup Blockchain + * @apiDescription Returns the hash of a block by height. + */ + async getBlockHash (req, res) { + try { + const heightRaw = req.params.height + if (!heightRaw) { + return res.status(400).json({ error: 'height can not be empty' }) + } + + const height = parseInt(heightRaw) + const result = await this.blockchainUseCases.getBlockHash({ height }) + + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in BlockchainRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ error: message }) + } +} + +export default BlockchainRESTController diff --git a/src/controllers/rest-api/full-node/blockchain/router.js b/src/controllers/rest-api/full-node/blockchain/router.js new file mode 100644 index 0000000..e07e3ae --- /dev/null +++ b/src/controllers/rest-api/full-node/blockchain/router.js @@ -0,0 +1,70 @@ +/* + REST API router for /full-node/blockchain routes. +*/ + +import express from 'express' +import BlockchainRESTController from './controller.js' + +class BlockchainRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Blockchain REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating Blockchain REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.blockchainController = new BlockchainRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/full-node/blockchain` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.blockchainController.root) + this.router.get('/getBestBlockHash', this.blockchainController.getBestBlockHash) + this.router.get('/getBlockchainInfo', this.blockchainController.getBlockchainInfo) + this.router.get('/getBlockCount', this.blockchainController.getBlockCount) + this.router.get('/getBlockHeader/:hash', this.blockchainController.getBlockHeaderSingle) + this.router.post('/getBlockHeader', this.blockchainController.getBlockHeaderBulk) + this.router.get('/getChainTips', this.blockchainController.getChainTips) + this.router.get('/getDifficulty', this.blockchainController.getDifficulty) + this.router.get('/getMempoolEntry/:txid', this.blockchainController.getMempoolEntrySingle) + this.router.post('/getMempoolEntry', this.blockchainController.getMempoolEntryBulk) + this.router.get('/getMempoolAncestors/:txid', this.blockchainController.getMempoolAncestorsSingle) + this.router.get('/getMempoolInfo', this.blockchainController.getMempoolInfo) + this.router.get('/getRawMempool', this.blockchainController.getRawMempool) + this.router.get('/getTxOut/:txid/:n', this.blockchainController.getTxOut) + this.router.post('/getTxOut', this.blockchainController.getTxOutPost) + this.router.get('/getTxOutProof/:txid', this.blockchainController.getTxOutProofSingle) + this.router.post('/getTxOutProof', this.blockchainController.getTxOutProofBulk) + this.router.get('/verifyTxOutProof/:proof', this.blockchainController.verifyTxOutProofSingle) + this.router.post('/verifyTxOutProof', this.blockchainController.verifyTxOutProofBulk) + this.router.post('/getBlock', this.blockchainController.getBlock) + this.router.get('/getBlockHash/:height', this.blockchainController.getBlockHash) + + app.use(this.baseUrl, this.router) + } +} + +export default BlockchainRouter diff --git a/src/controllers/rest-api/full-node/control/controller.js b/src/controllers/rest-api/full-node/control/controller.js new file mode 100644 index 0000000..4aa9d20 --- /dev/null +++ b/src/controllers/rest-api/full-node/control/controller.js @@ -0,0 +1,68 @@ +/* + REST API Controller for the /full-node/control routes. +*/ + +import wlogger from '../../../../adapters/wlogger.js' + +class ControlRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Control REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.control) { + throw new Error( + 'Instance of Control use cases required when instantiating Control REST Controller.' + ) + } + + this.controlUseCases = this.useCases.control + + this.root = this.root.bind(this) + this.getNetworkInfo = this.getNetworkInfo.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/full-node/control/ Service status + * @apiName ControlRoot + * @apiGroup Control + * + * @apiDescription Returns the status of the control service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'control' }) + } + + /** + * @api {get} /v6/full-node/control/getNetworkInfo Get Network Info + * @apiName GetNetworkInfo + * @apiGroup Control + * @apiDescription RPC call that gets basic full node information. + */ + async getNetworkInfo (req, res) { + try { + const result = await this.controlUseCases.getNetworkInfo() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in ControlRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ error: message }) + } +} + +export default ControlRESTController diff --git a/src/controllers/rest-api/full-node/control/router.js b/src/controllers/rest-api/full-node/control/router.js new file mode 100644 index 0000000..1fdd455 --- /dev/null +++ b/src/controllers/rest-api/full-node/control/router.js @@ -0,0 +1,51 @@ +/* + REST API router for /full-node/control routes. +*/ + +import express from 'express' +import ControlRESTController from './controller.js' + +class ControlRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Control REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating Control REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.controlController = new ControlRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/full-node/control` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.controlController.root) + this.router.get('/getNetworkInfo', this.controlController.getNetworkInfo) + + app.use(this.baseUrl, this.router) + } +} + +export default ControlRouter diff --git a/src/controllers/rest-api/full-node/dsproof/controller.js b/src/controllers/rest-api/full-node/dsproof/controller.js new file mode 100644 index 0000000..2d6122f --- /dev/null +++ b/src/controllers/rest-api/full-node/dsproof/controller.js @@ -0,0 +1,90 @@ +/* + REST API Controller for the /full-node/dsproof routes. +*/ + +import wlogger from '../../../../adapters/wlogger.js' + +class DSProofRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating DSProof REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.dsproof) { + throw new Error( + 'Instance of DSProof use cases required when instantiating DSProof REST Controller.' + ) + } + + this.dsproofUseCases = this.useCases.dsproof + + this.root = this.root.bind(this) + this.getDSProof = this.getDSProof.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/full-node/dsproof/ Service status + * @apiName DSProofRoot + * @apiGroup DSProof + * + * @apiDescription Returns the status of the dsproof service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'dsproof' }) + } + + /** + * @api {get} /v6/full-node/dsproof/getDSProof/:txid Get Double-Spend Proof + * @apiName GetDSProof + * @apiGroup DSProof + * @apiDescription Get information for a double-spend proof. + * + * @apiParam {String} txid Transaction ID + * @apiParam {String} verbose Verbose level (`false`, `true`) for compatibility with legacy API + */ + async getDSProof (req, res) { + try { + const txid = req.params.txid + + if (!txid) { + return res.status(400).json({ + success: false, + error: 'txid can not be empty' + }) + } + + if (txid.length !== 64) { + return res.status(400).json({ + success: false, + error: `txid must be of length 64 (not ${txid.length})` + }) + } + + let verbose = 2 + if (req.query.verbose === 'true') verbose = 3 + + const result = await this.dsproofUseCases.getDSProof({ txid, verbose }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in DSProofRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ error: message }) + } +} + +export default DSProofRESTController diff --git a/src/controllers/rest-api/full-node/dsproof/router.js b/src/controllers/rest-api/full-node/dsproof/router.js new file mode 100644 index 0000000..83626d9 --- /dev/null +++ b/src/controllers/rest-api/full-node/dsproof/router.js @@ -0,0 +1,51 @@ +/* + REST API router for /full-node/dsproof routes. +*/ + +import express from 'express' +import DSProofRESTController from './controller.js' + +class DSProofRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating DSProof REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating DSProof REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.dsproofController = new DSProofRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/full-node/dsproof` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.dsproofController.root) + this.router.get('/getDSProof/:txid', this.dsproofController.getDSProof) + + app.use(this.baseUrl, this.router) + } +} + +export default DSProofRouter diff --git a/src/controllers/rest-api/full-node/mining/controller.js b/src/controllers/rest-api/full-node/mining/controller.js new file mode 100644 index 0000000..bc98baa --- /dev/null +++ b/src/controllers/rest-api/full-node/mining/controller.js @@ -0,0 +1,99 @@ +/* + REST API Controller for the /full-node/mining routes. +*/ + +import wlogger from '../../../../adapters/wlogger.js' + +class MiningRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Mining REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.mining) { + throw new Error( + 'Instance of Mining use cases required when instantiating Mining REST Controller.' + ) + } + + this.miningUseCases = this.useCases.mining + + // Bind functions + this.root = this.root.bind(this) + this.getMiningInfo = this.getMiningInfo.bind(this) + this.getNetworkHashPS = this.getNetworkHashPS.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/full-node/mining/ Service status + * @apiName MiningRoot + * @apiGroup Mining + * + * @apiDescription Returns the status of the mining service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'mining' }) + } + + /** + * @api {get} /v6/full-node/mining/getMiningInfo Get Mining Info + * @apiName GetMiningInfo + * @apiGroup Mining + * @apiDescription Returns a json object containing mining-related information. + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/full-node/mining/getMiningInfo" -H "accept: application/json" + */ + async getMiningInfo (req, res) { + try { + const result = await this.miningUseCases.getMiningInfo() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/mining/getNetworkHashPS Get Estimated network hashes per second + * @apiName GetNetworkHashPS + * @apiGroup Mining + * @apiDescription Returns the estimated network hashes per second based on the last n blocks. Pass in [nblocks] to override # of blocks, -1 specifies since last difficulty change. Pass in [height] to estimate the network speed at the time when a certain block was found. + * + * @apiParam {Number} nblocks Number of blocks to use for estimation (default: 120) + * @apiParam {Number} height Block height to estimate at (default: -1) + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/full-node/mining/getNetworkHashPS?nblocks=120&height=-1" -H "accept: application/json" + */ + async getNetworkHashPS (req, res) { + try { + let nblocks = 120 // Default + let height = -1 // Default + if (req.query.nblocks) nblocks = parseInt(req.query.nblocks) + if (req.query.height) height = parseInt(req.query.height) + + const result = await this.miningUseCases.getNetworkHashPS({ nblocks, height }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in MiningRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ error: message }) + } +} + +export default MiningRESTController diff --git a/src/controllers/rest-api/full-node/mining/router.js b/src/controllers/rest-api/full-node/mining/router.js new file mode 100644 index 0000000..a71aead --- /dev/null +++ b/src/controllers/rest-api/full-node/mining/router.js @@ -0,0 +1,52 @@ +/* + REST API router for /full-node/mining routes. +*/ + +import express from 'express' +import MiningRESTController from './controller.js' + +class MiningRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Mining REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating Mining REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.miningController = new MiningRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/full-node/mining` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.miningController.root) + this.router.get('/getMiningInfo', this.miningController.getMiningInfo) + this.router.get('/getNetworkHashPS', this.miningController.getNetworkHashPS) + + app.use(this.baseUrl, this.router) + } +} + +export default MiningRouter diff --git a/src/controllers/rest-api/full-node/rawtransactions/controller.js b/src/controllers/rest-api/full-node/rawtransactions/controller.js new file mode 100644 index 0000000..26b59bc --- /dev/null +++ b/src/controllers/rest-api/full-node/rawtransactions/controller.js @@ -0,0 +1,333 @@ +/* + REST API Controller for the /full-node/rawtransactions routes. +*/ + +import wlogger from '../../../../adapters/wlogger.js' + +class RawTransactionsRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating RawTransactions REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.rawtransactions) { + throw new Error( + 'Instance of RawTransactions use cases required when instantiating RawTransactions REST Controller.' + ) + } + + this.rawtransactionsUseCases = this.useCases.rawtransactions + + // Bind functions + this.root = this.root.bind(this) + this.decodeRawTransactionSingle = this.decodeRawTransactionSingle.bind(this) + this.decodeRawTransactionBulk = this.decodeRawTransactionBulk.bind(this) + this.decodeScriptSingle = this.decodeScriptSingle.bind(this) + this.decodeScriptBulk = this.decodeScriptBulk.bind(this) + this.getRawTransactionSingle = this.getRawTransactionSingle.bind(this) + this.getRawTransactionBulk = this.getRawTransactionBulk.bind(this) + this.sendRawTransactionSingle = this.sendRawTransactionSingle.bind(this) + this.sendRawTransactionBulk = this.sendRawTransactionBulk.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/full-node/rawtransactions/ Service status + * @apiName RawTransactionsRoot + * @apiGroup RawTransactions + * + * @apiDescription Returns the status of the rawtransactions service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'rawtransactions' }) + } + + /** + * @api {get} /v6/full-node/rawtransactions/decodeRawTransaction/:hex Decode Single Raw Transaction + * @apiName DecodeSingleRawTransaction + * @apiGroup RawTransactions + * @apiDescription Return a JSON object representing the serialized, hex-encoded transaction. + * + * @apiParam {String} hex Hex-encoded transaction + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/full-node/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json" + */ + async decodeRawTransactionSingle (req, res) { + try { + const hex = req.params.hex + + if (!hex || hex === '') { + return res.status(400).json({ error: 'hex can not be empty' }) + } + + const result = await this.rawtransactionsUseCases.decodeRawTransaction({ hex }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/rawtransactions/decodeRawTransaction Decode Bulk Raw Transactions + * @apiName DecodeBulkRawTransactions + * @apiGroup RawTransactions + * @apiDescription Return bulk hex encoded transaction. + * + * @apiParam {String[]} hexes Array of hex-encoded transactions + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v6/full-node/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' + */ + async decodeRawTransactionBulk (req, res) { + try { + const hexes = req.body.hexes + + if (!Array.isArray(hexes)) { + return res.status(400).json({ error: 'hexes must be an array' }) + } + + if (!this.adapters.fullNode.validateArraySize(hexes.length)) { + return res.status(400).json({ error: 'Array too large.' }) + } + + // Validate each element in the array + for (const hex of hexes) { + if (!hex || hex === '') { + return res.status(400).json({ error: 'Encountered empty hex' }) + } + } + + const result = await this.rawtransactionsUseCases.decodeRawTransactions({ hexes }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/rawtransactions/decodeScript/:hex Decode Single Script + * @apiName DecodeSingleScript + * @apiGroup RawTransactions + * @apiDescription Decode a hex-encoded script. + * + * @apiParam {String} hex Hex-encoded script + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/full-node/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json" + */ + async decodeScriptSingle (req, res) { + try { + const hex = req.params.hex + + if (!hex || hex === '') { + return res.status(400).json({ error: 'hex can not be empty' }) + } + + const result = await this.rawtransactionsUseCases.decodeScript({ hex }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/rawtransactions/decodeScript Bulk Decode Script + * @apiName DecodeBulkScript + * @apiGroup RawTransactions + * @apiDescription Decode multiple hex-encoded scripts. + * + * @apiParam {String[]} hexes Array of hex-encoded scripts + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v6/full-node/rawtransactions/decodeScript" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' + */ + async decodeScriptBulk (req, res) { + try { + const hexes = req.body.hexes + + if (!Array.isArray(hexes)) { + return res.status(400).json({ error: 'hexes must be an array' }) + } + + if (!this.adapters.fullNode.validateArraySize(hexes.length)) { + return res.status(400).json({ error: 'Array too large.' }) + } + + // Validate each hex in the array + for (const hex of hexes) { + if (!hex || hex === '') { + return res.status(400).json({ error: 'Encountered empty hex' }) + } + } + + const result = await this.rawtransactionsUseCases.decodeScripts({ hexes }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/rawtransactions/getRawTransaction/:txid Get Raw Transaction + * @apiName GetRawTransaction + * @apiGroup RawTransactions + * @apiDescription Return the raw transaction data. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'. + * + * @apiParam {String} txid Transaction ID + * @apiParam {Boolean} verbose Return verbose data (default false) + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/full-node/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json" + */ + async getRawTransactionSingle (req, res) { + try { + const txid = req.params.txid + const verbose = req.query.verbose === 'true' + + if (!txid || txid === '') { + return res.status(400).json({ error: 'txid can not be empty' }) + } + + if (txid.length !== 64) { + return res.status(400).json({ + error: `parameter 1 must be of length 64 (not ${txid.length})` + }) + } + + const result = await this.rawtransactionsUseCases.getRawTransactionWithHeight({ txid, verbose }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/rawtransactions/getRawTransaction Get Bulk Raw Transactions + * @apiName GetBulkRawTransactions + * @apiGroup RawTransactions + * @apiDescription Return the raw transaction data for multiple transactions. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'. + * + * @apiParam {String[]} txids Array of transaction IDs + * @apiParam {Boolean} verbose Return verbose data (default false) + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v6/full-node/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}' + */ + async getRawTransactionBulk (req, res) { + try { + const txids = req.body.txids + const verbose = !!req.body.verbose + + if (!Array.isArray(txids)) { + return res.status(400).json({ error: 'txids must be an array' }) + } + + if (!this.adapters.fullNode.validateArraySize(txids.length)) { + return res.status(400).json({ error: 'Array too large.' }) + } + + // Validate each txid in the array + for (const txid of txids) { + if (!txid || txid === '') { + return res.status(400).json({ error: 'Encountered empty TXID' }) + } + + if (txid.length !== 64) { + return res.status(400).json({ + error: `parameter 1 must be of length 64 (not ${txid.length})` + }) + } + } + + const result = await this.rawtransactionsUseCases.getRawTransactions({ txids, verbose }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/full-node/rawtransactions/sendRawTransaction/:hex Send Single Raw Transaction + * @apiName SendSingleRawTransaction + * @apiGroup RawTransactions + * @apiDescription Submits single raw transaction (serialized, hex-encoded) to local node and network. + * + * @apiParam {String} hex Hex-encoded transaction + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/full-node/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: application/json" + */ + async sendRawTransactionSingle (req, res) { + try { + const hex = req.params.hex + + if (typeof hex !== 'string') { + return res.status(400).json({ error: 'hex must be a string' }) + } + + if (hex === '') { + return res.status(400).json({ error: 'Encountered empty hex' }) + } + + const result = await this.rawtransactionsUseCases.sendRawTransaction({ hex }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/full-node/rawtransactions/sendRawTransaction Send Bulk Raw Transactions + * @apiName SendBulkRawTransactions + * @apiGroup RawTransactions + * @apiDescription Submits multiple raw transaction (serialized, hex-encoded) to local node and network. + * + * @apiParam {String[]} hexes Array of hex-encoded transactions + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v6/full-node/rawtransactions/sendRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' + */ + async sendRawTransactionBulk (req, res) { + try { + const hexes = req.body.hexes + + if (!Array.isArray(hexes)) { + return res.status(400).json({ error: 'hex must be an array' }) + } + + if (!this.adapters.fullNode.validateArraySize(hexes.length)) { + return res.status(400).json({ error: 'Array too large.' }) + } + + // Validate each element + for (const hex of hexes) { + if (hex === '') { + return res.status(400).json({ error: 'Encountered empty hex' }) + } + } + + const result = await this.rawtransactionsUseCases.sendRawTransactions({ hexes }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in RawTransactionsRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ error: message }) + } +} + +export default RawTransactionsRESTController diff --git a/src/controllers/rest-api/full-node/rawtransactions/router.js b/src/controllers/rest-api/full-node/rawtransactions/router.js new file mode 100644 index 0000000..a06ecd8 --- /dev/null +++ b/src/controllers/rest-api/full-node/rawtransactions/router.js @@ -0,0 +1,58 @@ +/* + REST API router for /full-node/rawtransactions routes. +*/ + +import express from 'express' +import RawTransactionsRESTController from './controller.js' + +class RawTransactionsRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating RawTransactions REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating RawTransactions REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.rawtransactionsController = new RawTransactionsRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/full-node/rawtransactions` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.rawtransactionsController.root) + this.router.get('/decodeRawTransaction/:hex', this.rawtransactionsController.decodeRawTransactionSingle) + this.router.post('/decodeRawTransaction', this.rawtransactionsController.decodeRawTransactionBulk) + this.router.get('/decodeScript/:hex', this.rawtransactionsController.decodeScriptSingle) + this.router.post('/decodeScript', this.rawtransactionsController.decodeScriptBulk) + this.router.get('/getRawTransaction/:txid', this.rawtransactionsController.getRawTransactionSingle) + this.router.post('/getRawTransaction', this.rawtransactionsController.getRawTransactionBulk) + this.router.get('/sendRawTransaction/:hex', this.rawtransactionsController.sendRawTransactionSingle) + this.router.post('/sendRawTransaction', this.rawtransactionsController.sendRawTransactionBulk) + + app.use(this.baseUrl, this.router) + } +} + +export default RawTransactionsRouter diff --git a/src/controllers/rest-api/index.js b/src/controllers/rest-api/index.js new file mode 100644 index 0000000..7494c0b --- /dev/null +++ b/src/controllers/rest-api/index.js @@ -0,0 +1,94 @@ +/* + This index file for the Clean Architecture Controllers loads dependencies, + creates instances, and attaches the controller to REST API endpoints for + Express. +*/ + +// Local libraries +// import EventRouter from './event/index.js' +// import ReqRouter from './req/index.js' +import BlockchainRouter from './full-node/blockchain/router.js' +import ControlRouter from './full-node/control/router.js' +import DSProofRouter from './full-node/dsproof/router.js' +import EncryptionRouter from './encryption/router.js' +import FulcrumRouter from './fulcrum/router.js' +import MiningRouter from './full-node/mining/router.js' +import PriceRouter from './price/router.js' +import RawTransactionsRouter from './full-node/rawtransactions/router.js' +import SlpRouter from './slp/router.js' +import config from '../../config/index.js' + +class RESTControllers { + constructor (localConfig = {}) { + // Dependency Injection. + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating REST Controller libraries.' + ) + } + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating REST Controller libraries.' + ) + } + + // Allow overriding the API prefix for testing, default to v6. + this.apiPrefix = localConfig.apiPrefix || '/v6' + if (this.apiPrefix.length > 1 && this.apiPrefix.endsWith('/')) { + this.apiPrefix = this.apiPrefix.slice(0, -1) + } + + // Bind 'this' object to all subfunctions. + this.attachRESTControllers = this.attachRESTControllers.bind(this) + + // Encapsulate dependencies + this.config = config + } + + attachRESTControllers (app) { + const dependencies = { + adapters: this.adapters, + useCases: this.useCases, + apiPrefix: this.apiPrefix + } + + // Attach the REST API Controllers associated with the /event route + // const eventRouter = new EventRouter(dependencies) + // eventRouter.attach(app) + + // Attach the REST API Controllers associated with the /req route + // const reqRouter = new ReqRouter(dependencies) + // reqRouter.attach(app) + + const blockchainRouter = new BlockchainRouter(dependencies) + blockchainRouter.attach(app) + + const controlRouter = new ControlRouter(dependencies) + controlRouter.attach(app) + + const dsproofRouter = new DSProofRouter(dependencies) + dsproofRouter.attach(app) + + const encryptionRouter = new EncryptionRouter(dependencies) + encryptionRouter.attach(app) + + const fulcrumRouter = new FulcrumRouter(dependencies) + fulcrumRouter.attach(app) + + const miningRouter = new MiningRouter(dependencies) + miningRouter.attach(app) + + const priceRouter = new PriceRouter(dependencies) + priceRouter.attach(app) + + const rawtransactionsRouter = new RawTransactionsRouter(dependencies) + rawtransactionsRouter.attach(app) + + const slpRouter = new SlpRouter(dependencies) + slpRouter.attach(app) + } +} + +export default RESTControllers diff --git a/src/controllers/rest-api/price/controller.js b/src/controllers/rest-api/price/controller.js new file mode 100644 index 0000000..76dd401 --- /dev/null +++ b/src/controllers/rest-api/price/controller.js @@ -0,0 +1,96 @@ +/* + REST API Controller for the /price routes. +*/ + +import wlogger from '../../../adapters/wlogger.js' + +class PriceRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Price REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.price) { + throw new Error( + 'Instance of Price use cases required when instantiating Price REST Controller.' + ) + } + + this.priceUseCases = this.useCases.price + + // Bind functions + this.root = this.root.bind(this) + this.getBCHUSD = this.getBCHUSD.bind(this) + this.getPsffppWritePrice = this.getPsffppWritePrice.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/price/ Service status + * @apiName PriceRoot + * @apiGroup Price + * + * @apiDescription Returns the status of the price service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'price' }) + } + + /** + * @api {get} /v6/price/bchusd Get the USD price of BCH + * @apiName GetBCHUSD + * @apiGroup Price + * @apiDescription Get the USD price of BCH from Coinex. + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/price/bchusd" -H "accept: application/json" + * + * @apiSuccess {Number} usd The USD price of BCH + */ + async getBCHUSD (req, res) { + try { + const price = await this.priceUseCases.getBCHUSD() + return res.status(200).json({ usd: price }) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {get} /v6/price/psffpp Get the PSF price for writing to the PSFFPP + * @apiName GetPsffppWritePrice + * @apiGroup Price + * @apiDescription Get the price to pin 1MB of content to the PSFFPP pinning + * network on IPFS. The price is denominated in PSF tokens. + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v6/price/psffpp" -H "accept: application/json" + * + * @apiSuccess {Number} writePrice The price in PSF tokens to write 1MB to PSFFPP + */ + async getPsffppWritePrice (req, res) { + try { + const writePrice = await this.priceUseCases.getPsffppWritePrice() + return res.status(200).json({ writePrice }) + } catch (err) { + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in PriceRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ error: message }) + } +} + +export default PriceRESTController diff --git a/src/controllers/rest-api/price/router.js b/src/controllers/rest-api/price/router.js new file mode 100644 index 0000000..65e332d --- /dev/null +++ b/src/controllers/rest-api/price/router.js @@ -0,0 +1,52 @@ +/* + REST API router for /price routes. +*/ + +import express from 'express' +import PriceRESTController from './controller.js' + +class PriceRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating Price REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating Price REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.priceController = new PriceRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/price` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.priceController.root) + this.router.get('/bchusd', this.priceController.getBCHUSD) + this.router.get('/psffpp', this.priceController.getPsffppWritePrice) + + app.use(this.baseUrl, this.router) + } +} + +export default PriceRouter diff --git a/src/controllers/rest-api/slp/controller.js b/src/controllers/rest-api/slp/controller.js new file mode 100644 index 0000000..a5837e0 --- /dev/null +++ b/src/controllers/rest-api/slp/controller.js @@ -0,0 +1,220 @@ +/* + REST API Controller for the /slp routes. +*/ + +import wlogger from '../../../adapters/wlogger.js' +import BCHJS from '@psf/bch-js' +import config from '../../../config/index.js' + +const bchjs = new BCHJS({ restURL: config.restURL }) + +class SlpRESTController { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating SLP REST Controller.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases || !this.useCases.slp) { + throw new Error( + 'Instance of SLP use cases required when instantiating SLP REST Controller.' + ) + } + + this.slpUseCases = this.useCases.slp + + // Bind functions + this.root = this.root.bind(this) + this.getStatus = this.getStatus.bind(this) + this.getAddress = this.getAddress.bind(this) + this.getTxid = this.getTxid.bind(this) + this.getTokenStats = this.getTokenStats.bind(this) + this.getTokenData = this.getTokenData.bind(this) + this.handleError = this.handleError.bind(this) + } + + /** + * @api {get} /v6/slp/ Service status + * @apiName SlpRoot + * @apiGroup SLP + * + * @apiDescription Returns the status of the SLP service. + * + * @apiSuccess {String} status Service identifier + */ + async root (req, res) { + return res.status(200).json({ status: 'psf-slp-indexer' }) + } + + /** + * Validates and converts an address to cash address format + * @param {string} address - Address to validate and convert + * @returns {string} Cash address + * @throws {Error} If address is invalid or not mainnet + */ + _validateAndConvertAddress (address) { + if (!address) { + throw new Error('address is empty') + } + + // Convert legacy to cash address + const cashAddr = bchjs.SLP.Address.toCashAddress(address) + + // Ensure it's a valid BCH address + try { + bchjs.SLP.Address.toLegacyAddress(cashAddr) + } catch (err) { + throw new Error(`Invalid BCH address. Double check your address is valid: ${address}`) + } + + // Ensure it's mainnet (no testnet support) + const isMainnet = bchjs.Address.isMainnetAddress(cashAddr) + if (!isMainnet) { + throw new Error('Invalid network. Only mainnet addresses are supported.') + } + + return cashAddr + } + + /** + * @api {get} /v6/slp/status Get indexer status + * @apiName GetStatus + * @apiGroup SLP + * @apiDescription Returns the status of the SLP indexer. + */ + async getStatus (req, res) { + try { + const result = await this.slpUseCases.getStatus() + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/slp/address Get SLP balance for address + * @apiName GetAddress + * @apiGroup SLP + * @apiDescription Returns SLP balance for an address. + */ + async getAddress (req, res) { + try { + const address = req.body.address + + if (!address || address === '') { + return res.status(400).json({ + success: false, + error: 'address can not be empty' + }) + } + + // Validate and convert address + const cashAddr = this._validateAndConvertAddress(address) + + const result = await this.slpUseCases.getAddress({ address: cashAddr }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/slp/txid Get SLP transaction data + * @apiName GetTxid + * @apiGroup SLP + * @apiDescription Returns SLP transaction data for a TXID. + */ + async getTxid (req, res) { + try { + const txid = req.body.txid + + if (!txid || txid === '') { + return res.status(400).json({ + success: false, + error: 'txid can not be empty' + }) + } + + if (txid.length !== 64) { + return res.status(400).json({ + success: false, + error: 'This is not a txid' + }) + } + + const result = await this.slpUseCases.getTxid({ txid }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/slp/token Get token statistics + * @apiName GetTokenStats + * @apiGroup SLP + * @apiDescription Returns statistics for a single SLP token. + */ + async getTokenStats (req, res) { + try { + const tokenId = req.body.tokenId + + if (!tokenId || tokenId === '') { + return res.status(400).json({ + success: false, + error: 'tokenId can not be empty' + }) + } + + // Flag to toggle tx history of the token + const withTxHistory = req.body.withTxHistory === true + + const result = await this.slpUseCases.getTokenStats({ tokenId, withTxHistory }) + return res.status(200).json(result) + } catch (err) { + return this.handleError(err, res) + } + } + + /** + * @api {post} /v6/slp/token/data Get token data + * @apiName GetTokenData + * @apiGroup SLP + * @apiDescription Get mutable and immutable data if the token contains them. + */ + async getTokenData (req, res) { + try { + const tokenId = req.body.tokenId + + if (!tokenId || tokenId === '') { + return res.status(400).json({ + success: false, + error: 'tokenId can not be empty' + }) + } + + // Flag to toggle tx history of the token + const withTxHistory = req.body.withTxHistory === true + + const result = await this.slpUseCases.getTokenData({ tokenId, withTxHistory }) + return res.status(200).json(result) + } catch (err) { + console.log('Error in /v6/slp/token/data getTokenData(): ', err) + return this.handleError(err, res) + } + } + + handleError (err, res) { + wlogger.error('Error in SlpRESTController:', err) + + const status = err.status || 500 + const message = err.message || 'Internal server error' + + return res.status(status).json({ error: message }) + } +} + +export default SlpRESTController diff --git a/src/controllers/rest-api/slp/router.js b/src/controllers/rest-api/slp/router.js new file mode 100644 index 0000000..32c7f97 --- /dev/null +++ b/src/controllers/rest-api/slp/router.js @@ -0,0 +1,55 @@ +/* + REST API router for /slp routes. +*/ + +import express from 'express' +import SlpRESTController from './controller.js' + +class SlpRouter { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating SLP REST Router.' + ) + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating SLP REST Router.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + this.slpController = new SlpRESTController(dependencies) + + this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '') + this.baseUrl = `${this.apiPrefix}/slp` + if (!this.baseUrl.startsWith('/')) { + this.baseUrl = `/${this.baseUrl}` + } + this.router = express.Router() + } + + attach (app) { + if (!app) { + throw new Error('Must pass app object when attaching REST API controllers.') + } + + this.router.get('/', this.slpController.root) + this.router.get('/status', this.slpController.getStatus) + this.router.post('/address', this.slpController.getAddress) + this.router.post('/txid', this.slpController.getTxid) + this.router.post('/token', this.slpController.getTokenStats) + this.router.post('/token/data', this.slpController.getTokenData) + + app.use(this.baseUrl, this.router) + } +} + +export default SlpRouter diff --git a/src/controllers/timer-controller.js b/src/controllers/timer-controller.js new file mode 100644 index 0000000..fa13c79 --- /dev/null +++ b/src/controllers/timer-controller.js @@ -0,0 +1,72 @@ +/* + Timer-based controller functions. + This controller handles scheduled tasks and timers. +*/ + +// Local libraries +import wlogger from '../adapters/wlogger.js' + +class TimerController { + constructor (localConfig = {}) { + // Dependency Injection + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating TimerController.' + ) + } + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating TimerController.' + ) + } + + // Constants + this.SHUTDOWN_INTERVAL_MS = 10 * 60 * 60 * 1000 // 10 hours in milliseconds + this.LIVENESS_CHECK_INTERVAL_MS = 1 * 60 * 1000 // 1 minute in milliseconds + + // Handlers + this.shutdownHandler = null + this.livenessCheckHandler = null + + // Bind 'this' object to all subfunctions + this.startTimerControllers = this.startTimerControllers.bind(this) + this.stopTimerControllers = this.stopTimerControllers.bind(this) + this.shutdown = this.shutdown.bind(this) + this.livenessCheck = this.livenessCheck.bind(this) + } + + startTimerControllers () { + console.log('Starting Timer Controllers.') + + // this.shutdownHandler = setInterval(() => { + // this.shutdown() + // }, this.SHUTDOWN_INTERVAL_MS) + + // this.livenessCheckHandler = setInterval(() => { + // this.livenessCheck() + // }, this.LIVENESS_CHECK_INTERVAL_MS) + } + + stopTimerControllers () { + console.log('Stopping Timer Controllers.') + + clearInterval(this.shutdownHandler) + this.shutdownHandler = null + clearInterval(this.livenessCheckHandler) + this.livenessCheckHandler = null + } + + // Execute the shutdown callback + shutdown () { + wlogger.info(`TimerController: Shutting down application at ${new Date().toISOString()}, depending on process manager to restart application.`) + process.exit(1) + } + + livenessCheck () { + wlogger.info(`TimerController: Liveness check at ${new Date().toISOString()}`) + } +} + +export default TimerController diff --git a/src/middleware/basic-auth.js b/src/middleware/basic-auth.js new file mode 100644 index 0000000..5c9f2e9 --- /dev/null +++ b/src/middleware/basic-auth.js @@ -0,0 +1,61 @@ +/* + Basic Authentication Middleware + + This middleware validates Bearer tokens from the Authorization header. + When a valid token is provided, it sets req.locals.basicAuthValid = true + to allow bypassing x402 middleware. +*/ + +import config from '../config/index.js' +import wlogger from '../adapters/wlogger.js' + +/** + * Middleware function that validates Bearer token authentication + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + */ +export function basicAuthMiddleware (req, res, next) { + // Initialize req.locals if it doesn't exist + if (!req.locals) { + req.locals = {} + } + + // Default to false + req.locals.basicAuthValid = false + + // Get the configured token + const configuredToken = config.basicAuth?.token + + // If no token is configured, skip validation + if (!configuredToken) { + wlogger.warn('Basic auth enabled but no BASIC_AUTH_TOKEN configured') + return next() + } + + // Get the Authorization header + const authHeader = req.headers.authorization + + // If no Authorization header, continue (x402 will handle unauthorized requests) + if (!authHeader) { + return next() + } + + // Check if it's a Bearer token + const parts = authHeader.split(' ') + if (parts.length !== 2 || parts[0] !== 'Bearer') { + return next() + } + + const providedToken = parts[1] + + // Compare tokens + if (providedToken === configuredToken) { + req.locals.basicAuthValid = true + wlogger.verbose(`Basic auth validated for request to ${req.path}`) + } + + // Always continue to next middleware + // If auth failed, x402 middleware will handle the request + next() +} diff --git a/src/use-cases/encryption-use-cases.js b/src/use-cases/encryption-use-cases.js new file mode 100644 index 0000000..77ef1fd --- /dev/null +++ b/src/use-cases/encryption-use-cases.js @@ -0,0 +1,120 @@ +/* + Use cases for encryption-related operations. + Retrieves public keys from the blockchain for BCH addresses. +*/ + +// Global npm libraries +import BCHJS from '@psf/bch-js' + +// Local libraries +import wlogger from '../adapters/wlogger.js' +import config from '../config/index.js' + +const bchjs = new BCHJS({ restURL: config.restURL }) + +class EncryptionUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating Encryption use cases.') + } + + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error('UseCases instance required when instantiating Encryption use cases.') + } + + // Allow bchjs to be injected for testing + this.bchjs = localConfig.bchjs || bchjs + } + + /** + * Get the public key for a BCH address by searching the blockchain. + * Searches the transaction history of the address for a transaction input + * that contains the public key. + * + * @param {Object} params - Parameters object + * @param {string} params.address - BCH address (cash address or legacy format) + * @returns {Promise} Object with success status and publicKey if found + */ + async getPublicKey ({ address }) { + try { + // Convert to cash address format + const cashAddr = this.bchjs.Address.toCashAddress(address) + + // Get transaction history for the address + const txHistory = await this.useCases.fulcrum.getTransactions({ address: cashAddr }) + + // Extract just the TXIDs + const txids = txHistory.transactions.map((elem) => elem.tx_hash) + + // Throw error if there is no transaction history + if (!txids || txids.length === 0) { + throw new Error('No transaction history.') + } + + // Loop through the transaction history and search for the public key + for (let i = 0; i < txids.length; i++) { + const thisTx = txids[i] + + // Get verbose transaction details + const txDetails = await this.useCases.rawtransactions.getRawTransaction({ + txid: thisTx, + verbose: true + }) + + const vin = txDetails.vin + + // Loop through each input + for (let j = 0; j < vin.length; j++) { + const thisVin = vin[j] + + // Skip if no scriptSig (e.g., coinbase transactions) + if (!thisVin.scriptSig || !thisVin.scriptSig.asm) { + continue + } + + // Extract the script signature + const scriptSig = thisVin.scriptSig.asm.split(' ') + + // Extract the public key from the script signature (last element) + const pubKey = scriptSig[scriptSig.length - 1] + + // Skip if pubKey is not a valid hex string (basic validation) + if (!pubKey || !/^[0-9a-fA-F]+$/.test(pubKey)) { + continue + } + + try { + // Generate cash address from public key + const keyBuf = Buffer.from(pubKey, 'hex') + const ec = this.bchjs.ECPair.fromPublicKey(keyBuf) + const cashAddr2 = this.bchjs.ECPair.toCashAddress(ec) + + // If public keys match, this is the correct public key + if (cashAddr === cashAddr2) { + return { + success: true, + publicKey: pubKey + } + } + } catch (err) { + // Skip invalid public keys - continue searching + continue + } + } + } + + // Public key not found in any transaction + return { + success: false, + publicKey: 'not found' + } + } catch (err) { + wlogger.error('Error in EncryptionUseCases.getPublicKey()', err) + throw err + } + } +} + +export default EncryptionUseCases diff --git a/src/use-cases/fulcrum-use-cases.js b/src/use-cases/fulcrum-use-cases.js new file mode 100644 index 0000000..e019956 --- /dev/null +++ b/src/use-cases/fulcrum-use-cases.js @@ -0,0 +1,166 @@ +/* + Use cases for interacting with the Fulcrum API service. +*/ + +import wlogger from '../adapters/wlogger.js' +import BCHJS from '@psf/bch-js' +import config from '../config/index.js' + +const bchjs = new BCHJS({ + restURL: config.restURL, + bearerToken: config.basicAuth.token +}) + +class FulcrumUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating Fulcrum use cases.') + } + + this.fulcrum = this.adapters.fulcrum + if (!this.fulcrum) { + throw new Error('Fulcrum adapter required when instantiating Fulcrum use cases.') + } + + // Allow bchjs to be injected for testing + this.bchjs = localConfig.bchjs || bchjs + } + + async getBalance ({ address }) { + return this.fulcrum.get(`electrumx/balance/${address}`) + } + + async getBalances ({ addresses }) { + try { + const response = await this.fulcrum.post('electrumx/balance/', { addresses }) + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.getBalances()', err) + throw err + } + } + + async getUtxos ({ address }) { + return this.fulcrum.get(`electrumx/utxos/${address}`) + } + + async getUtxosBulk ({ addresses }) { + try { + const response = await this.fulcrum.post('electrumx/utxos/', { addresses }) + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.getUtxosBulk()', err) + throw err + } + } + + async getTransactionDetails ({ txid }) { + try { + const response = await this.fulcrum.get(`electrumx/tx/data/${txid}`) + // console.log(`getTransactionDetails() TXID ${txid}: ${JSON.stringify(response, null, 2)}`) + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.getTransactionDetails()', err) + throw err + } + } + + async getTransactionDetailsBulk ({ txids, verbose }) { + try { + const response = await this.fulcrum.post('electrumx/tx/data', { txids, verbose }) + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.getTransactionDetailsBulk()', err) + throw err + } + } + + async broadcastTransaction ({ txHex }) { + try { + const response = await this.fulcrum.post('electrumx/tx/broadcast', { txHex }) + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.broadcastTransaction()', err) + throw err + } + } + + async getBlockHeaders ({ height, count }) { + return this.fulcrum.get(`electrumx/block/headers/${height}?count=${count}`) + } + + async getBlockHeadersBulk ({ heights }) { + try { + const response = await this.fulcrum.post('electrumx/block/headers', { heights }) + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.getBlockHeadersBulk()', err) + throw err + } + } + + async getTransactions ({ address, allTxs }) { + try { + const response = await this.fulcrum.get(`electrumx/transactions/${address}`) + + // Sort transactions in descending order, so that newest transactions are first. + if (response.transactions && Array.isArray(response.transactions)) { + response.transactions = await this.bchjs.Electrumx.sortAllTxs(response.transactions, 'DESCENDING') + + if (!allTxs) { + // Return only the first 100 transactions of the history. + response.transactions = response.transactions.slice(0, 100) + } + } + + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.getTransactions()', err) + throw err + } + } + + async getTransactionsBulk ({ addresses, allTxs }) { + try { + const response = await this.fulcrum.post('electrumx/transactions/', { addresses }) + + // Sort transactions in descending order for each address entry. + if (response.transactions && Array.isArray(response.transactions)) { + for (let i = 0; i < response.transactions.length; i++) { + const thisEntry = response.transactions[i] + if (thisEntry.transactions && Array.isArray(thisEntry.transactions)) { + thisEntry.transactions = await this.bchjs.Electrumx.sortAllTxs(thisEntry.transactions, 'DESCENDING') + + if (!allTxs && thisEntry.transactions.length > 100) { + // Extract only the first 100 transactions. + thisEntry.transactions = thisEntry.transactions.slice(0, 100) + } + } + } + } + + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.getTransactionsBulk()', err) + throw err + } + } + + async getMempool ({ address }) { + return this.fulcrum.get(`electrumx/unconfirmed/${address}`) + } + + async getMempoolBulk ({ addresses }) { + try { + const response = await this.fulcrum.post('electrumx/unconfirmed/', { addresses }) + return response + } catch (err) { + wlogger.error('Error in FulcrumUseCases.getMempoolBulk()', err) + throw err + } + } +} + +export default FulcrumUseCases diff --git a/src/use-cases/full-node-blockchain-use-cases.js b/src/use-cases/full-node-blockchain-use-cases.js new file mode 100644 index 0000000..1986ff6 --- /dev/null +++ b/src/use-cases/full-node-blockchain-use-cases.js @@ -0,0 +1,134 @@ +/* + Use cases for interacting with the BCH full node blockchain RPC interface. +*/ + +import wlogger from '../adapters/wlogger.js' + +class BlockchainUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating Blockchain use cases.') + } + + this.fullNode = this.adapters.fullNode + if (!this.fullNode) { + throw new Error('Full node adapter required when instantiating Blockchain use cases.') + } + } + + async getBestBlockHash () { + return this.fullNode.call('getbestblockhash') + } + + async getBlockchainInfo () { + return this.fullNode.call('getblockchaininfo') + } + + async getBlockCount () { + return this.fullNode.call('getblockcount') + } + + async getBlockHeader ({ hash, verbose = false }) { + return this.fullNode.call('getblockheader', [hash, verbose]) + } + + async getBlockHeaders ({ hashes, verbose = false }) { + try { + const promises = hashes.map(hash => + this.fullNode.call('getblockheader', [hash, verbose], `getblockheader-${hash}`) + ) + + return await Promise.all(promises) + } catch (err) { + wlogger.error('Error in BlockchainUseCases.getBlockHeaders()', err) + throw err + } + } + + async getChainTips () { + return this.fullNode.call('getchaintips') + } + + async getDifficulty () { + return this.fullNode.call('getdifficulty') + } + + async getMempoolEntry ({ txid }) { + return this.fullNode.call('getmempoolentry', [txid]) + } + + async getMempoolEntries ({ txids }) { + try { + const promises = txids.map(txid => + this.fullNode.call('getmempoolentry', [txid], `getmempoolentry-${txid}`) + ) + + return await Promise.all(promises) + } catch (err) { + wlogger.error('Error in BlockchainUseCases.getMempoolEntries()', err) + throw err + } + } + + async getMempoolAncestors ({ txid, verbose = false }) { + return this.fullNode.call('getmempoolancestors', [txid, verbose]) + } + + async getMempoolInfo () { + return this.fullNode.call('getmempoolinfo') + } + + async getRawMempool ({ verbose = false }) { + return this.fullNode.call('getrawmempool', [verbose]) + } + + async getTxOut ({ txid, n, includeMempool }) { + return this.fullNode.call('gettxout', [txid, n, includeMempool]) + } + + async getTxOutProof ({ txid }) { + return this.fullNode.call('gettxoutproof', [[txid]]) + } + + async getTxOutProofs ({ txids }) { + try { + const promises = txids.map(txid => + this.fullNode.call('gettxoutproof', [[txid]], `gettxoutproof-${txid}`) + ) + + return await Promise.all(promises) + } catch (err) { + wlogger.error('Error in BlockchainUseCases.getTxOutProofs()', err) + throw err + } + } + + async verifyTxOutProof ({ proof }) { + return this.fullNode.call('verifytxoutproof', [proof]) + } + + async verifyTxOutProofs ({ proofs }) { + try { + const promises = proofs.map(proof => + this.fullNode.call('verifytxoutproof', [proof], `verifytxoutproof-${proof.slice(0, 16)}`) + ) + + return await Promise.all(promises) + } catch (err) { + wlogger.error('Error in BlockchainUseCases.verifyTxOutProofs()', err) + throw err + } + } + + async getBlock ({ blockhash, verbosity }) { + return this.fullNode.call('getblock', [blockhash, verbosity]) + } + + async getBlockHash ({ height }) { + return this.fullNode.call('getblockhash', [height]) + } +} + +export default BlockchainUseCases diff --git a/src/use-cases/full-node-control-use-cases.js b/src/use-cases/full-node-control-use-cases.js new file mode 100644 index 0000000..511cf65 --- /dev/null +++ b/src/use-cases/full-node-control-use-cases.js @@ -0,0 +1,24 @@ +/* + Use cases for interacting with the BCH full node control RPC interface. +*/ + +class ControlUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating Control use cases.') + } + + this.fullNode = this.adapters.fullNode + if (!this.fullNode) { + throw new Error('Full node adapter required when instantiating Control use cases.') + } + } + + async getNetworkInfo () { + return this.fullNode.call('getnetworkinfo') + } +} + +export default ControlUseCases diff --git a/src/use-cases/full-node-dsproof-use-cases.js b/src/use-cases/full-node-dsproof-use-cases.js new file mode 100644 index 0000000..f4fb0cc --- /dev/null +++ b/src/use-cases/full-node-dsproof-use-cases.js @@ -0,0 +1,24 @@ +/* + Use cases for interacting with the BCH full node double-spend proof RPC interface. +*/ + +class DSProofUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating DSProof use cases.') + } + + this.fullNode = this.adapters.fullNode + if (!this.fullNode) { + throw new Error('Full node adapter required when instantiating DSProof use cases.') + } + } + + async getDSProof ({ txid, verbose }) { + return this.fullNode.call('getdsproof', [txid, verbose]) + } +} + +export default DSProofUseCases diff --git a/src/use-cases/full-node-mining-use-cases.js b/src/use-cases/full-node-mining-use-cases.js new file mode 100644 index 0000000..f2deba8 --- /dev/null +++ b/src/use-cases/full-node-mining-use-cases.js @@ -0,0 +1,28 @@ +/* + Use cases for interacting with the BCH full node mining RPC interface. +*/ + +class MiningUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating Mining use cases.') + } + + this.fullNode = this.adapters.fullNode + if (!this.fullNode) { + throw new Error('Full node adapter required when instantiating Mining use cases.') + } + } + + async getMiningInfo () { + return this.fullNode.call('getmininginfo') + } + + async getNetworkHashPS ({ nblocks, height }) { + return this.fullNode.call('getnetworkhashps', [nblocks, height]) + } +} + +export default MiningUseCases diff --git a/src/use-cases/full-node-rawtransactions-use-cases.js b/src/use-cases/full-node-rawtransactions-use-cases.js new file mode 100644 index 0000000..6d4b304 --- /dev/null +++ b/src/use-cases/full-node-rawtransactions-use-cases.js @@ -0,0 +1,121 @@ +/* + Use cases for interacting with the BCH full node raw transactions RPC interface. +*/ + +import wlogger from '../adapters/wlogger.js' + +class RawTransactionsUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating RawTransactions use cases.') + } + + this.fullNode = this.adapters.fullNode + if (!this.fullNode) { + throw new Error('Full node adapter required when instantiating RawTransactions use cases.') + } + } + + async decodeRawTransaction ({ hex }) { + return this.fullNode.call('decoderawtransaction', [hex]) + } + + async decodeRawTransactions ({ hexes }) { + try { + const promises = hexes.map(hex => + this.fullNode.call('decoderawtransaction', [hex], `decoderawtransaction-${hex.slice(0, 16)}`) + ) + + return await Promise.all(promises) + } catch (err) { + wlogger.error('Error in RawTransactionsUseCases.decodeRawTransactions()', err) + throw err + } + } + + async decodeScript ({ hex }) { + return this.fullNode.call('decodescript', [hex]) + } + + async decodeScripts ({ hexes }) { + try { + const promises = hexes.map(hex => + this.fullNode.call('decodescript', [hex], `decodescript-${hex.slice(0, 16)}`) + ) + + return await Promise.all(promises) + } catch (err) { + wlogger.error('Error in RawTransactionsUseCases.decodeScripts()', err) + throw err + } + } + + async getRawTransaction ({ txid, verbose = false }) { + const verboseInt = verbose ? 1 : 0 + return this.fullNode.call('getrawtransaction', [txid, verboseInt]) + } + + async getRawTransactions ({ txids, verbose = false }) { + try { + const verboseInt = verbose ? 1 : 0 + const promises = txids.map(txid => + this.fullNode.call('getrawtransaction', [txid, verboseInt], `getrawtransaction-${txid}`) + ) + + return await Promise.all(promises) + } catch (err) { + wlogger.error('Error in RawTransactionsUseCases.getRawTransactions()', err) + throw err + } + } + + async getRawTransactionWithHeight ({ txid, verbose = false }) { + const verboseInt = verbose ? 1 : 0 + const data = await this.fullNode.call('getrawtransaction', [txid, verboseInt]) + + if (verbose && data && data.blockhash) { + data.height = null + try { + // Look up the block height and append it to the TX response. + const blockHeader = await this.fullNode.call('getblockheader', [data.blockhash, true]) + data.height = blockHeader.height + } catch (err) { + // Exit quietly if block header lookup fails + wlogger.debug('Could not fetch block header for height lookup', err) + } + } + + return data + } + + async getBlockHeader ({ blockHash, verbose = false }) { + return this.fullNode.call('getblockheader', [blockHash, verbose]) + } + + async sendRawTransaction ({ hex }) { + return this.fullNode.call('sendrawtransaction', [hex]) + } + + async sendRawTransactions ({ hexes }) { + // Dev Note: Sending the 'sendrawtransaction' RPC call to a full node in parallel will + // not work. Testing showed that the full node will return the same TXID for + // different TX hexes. I believe this is by design, to prevent double spends. + // In parallel, we are essentially asking the node to broadcast a new TX before + // it's finished broadcasting the previous one. Serial execution is required. + try { + const result = [] + for (const hex of hexes) { + const txid = await this.fullNode.call('sendrawtransaction', [hex], `sendrawtransaction-${hex.slice(0, 16)}`) + result.push(txid) + } + return result + } catch (err) { + wlogger.error('Error in RawTransactionsUseCases.sendRawTransactions()', err) + throw err + } + } +} + +export default RawTransactionsUseCases diff --git a/src/use-cases/index.js b/src/use-cases/index.js new file mode 100644 index 0000000..7021cb9 --- /dev/null +++ b/src/use-cases/index.js @@ -0,0 +1,50 @@ +/* + This is a top-level library that encapsulates all the additional Use Cases. + The concept of Use Cases comes from Clean Architecture: + https://troutsblog.com/blog/clean-architecture +*/ + +// Local libraries +import BlockchainUseCases from './full-node-blockchain-use-cases.js' +import ControlUseCases from './full-node-control-use-cases.js' +import DSProofUseCases from './full-node-dsproof-use-cases.js' +import EncryptionUseCases from './encryption-use-cases.js' +import FulcrumUseCases from './fulcrum-use-cases.js' +import MiningUseCases from './full-node-mining-use-cases.js' +import PriceUseCases from './price-use-cases.js' +import RawTransactionsUseCases from './full-node-rawtransactions-use-cases.js' +import SlpUseCases from './slp-use-cases.js' + +class UseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of adapters must be passed in when instantiating Use Cases library.' + ) + } + + this.blockchain = new BlockchainUseCases({ adapters: this.adapters }) + this.control = new ControlUseCases({ adapters: this.adapters }) + this.dsproof = new DSProofUseCases({ adapters: this.adapters }) + this.fulcrum = new FulcrumUseCases({ adapters: this.adapters }) + this.mining = new MiningUseCases({ adapters: this.adapters }) + this.price = new PriceUseCases({ adapters: this.adapters }) + this.rawtransactions = new RawTransactionsUseCases({ adapters: this.adapters }) + this.slp = new SlpUseCases({ adapters: this.adapters }) + + // Encryption use cases require access to other use cases (fulcrum, rawtransactions) + this.encryption = new EncryptionUseCases({ + adapters: this.adapters, + useCases: this + }) + } + + // Run any startup Use Cases at the start of the app. + async start () { + console.log('Use Cases have been started.') + return true + } +} + +export default UseCases diff --git a/src/use-cases/price-use-cases.js b/src/use-cases/price-use-cases.js new file mode 100644 index 0000000..2bbf4a2 --- /dev/null +++ b/src/use-cases/price-use-cases.js @@ -0,0 +1,83 @@ +/* + Use cases for price-related operations. +*/ + +// Global npm libraries +import axios from 'axios' +import SlpWallet from 'minimal-slp-wallet' +import PSFFPP from 'psffpp' + +// Local libraries +import wlogger from '../adapters/wlogger.js' +import config from '../config/index.js' + +class PriceUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating Price use cases.') + } + + // Get config + this.config = localConfig.config || config + + // Coinex API URL for BCH/USDT + this.bchCoinexPriceUrl = + 'https://api.coinex.com/v1/market/ticker?market=bchusdt' + + // Allow axios to be injected for testing + this.axios = localConfig.axios || axios + } + + /** + * Get the USD price of BCH from Coinex. + * @returns {Promise} The USD price of BCH + */ + async getBCHUSD () { + try { + // Request options + const opt = { + method: 'get', + baseURL: this.bchCoinexPriceUrl, + timeout: 15000 + } + + const response = await this.axios.request(opt) + + const price = Number(response.data.data.ticker.last) + + return price + } catch (err) { + wlogger.error('Error in PriceUseCases.getBCHUSD()', err) + throw err + } + } + + /** + * Get the PSF price for writing to the PSFFPP. + * Returns the price to pin 1MB of content to the PSFFPP pinning + * network on IPFS. The price is denominated in PSF tokens. + * @returns {Promise} The write price in PSF tokens + */ + async getPsffppWritePrice () { + try { + const wallet = new SlpWallet(undefined, { + interface: 'rest-api', + restURL: this.config.restURL + }) + await wallet.walletInfoPromise + + const psffpp = new PSFFPP({ wallet }) + + const writePrice = await psffpp.getMcWritePrice() + + return writePrice + } catch (err) { + wlogger.error('Error in PriceUseCases.getPsffppWritePrice()', err) + throw err + } + } +} + +export default PriceUseCases diff --git a/src/use-cases/slp-use-cases.js b/src/use-cases/slp-use-cases.js new file mode 100644 index 0000000..406f7ce --- /dev/null +++ b/src/use-cases/slp-use-cases.js @@ -0,0 +1,325 @@ +/* + Use cases for interacting with the SLP Indexer API service. +*/ + +import wlogger from '../adapters/wlogger.js' +import BCHJS from '@psf/bch-js' +import SlpWallet from 'minimal-slp-wallet' +import SlpTokenMedia from 'slp-token-media' +import axios from 'axios' +import config from '../config/index.js' + +const bchjs = new BCHJS({ restURL: config.restURL }) + +class SlpUseCases { + constructor (localConfig = {}) { + this.adapters = localConfig.adapters + + if (!this.adapters) { + throw new Error('Adapters instance required when instantiating SLP use cases.') + } + + this.slpIndexer = this.adapters.slpIndexer + if (!this.slpIndexer) { + throw new Error('SLP Indexer adapter required when instantiating SLP use cases.') + } + + // Allow bchjs to be injected for testing + this.bchjs = localConfig.bchjs || bchjs + + // Get config + this.config = localConfig.config || config + + // Initialize wallet (lazy initialization) + this.wallet = null + this.slpTokenMedia = null + this.walletInitialized = false + this.initializationPromise = null + } + + // Initialize wallet and SlpTokenMedia asynchronously + async _ensureInitialized () { + if (this.walletInitialized) { + return + } + + if (this.initializationPromise) { + return this.initializationPromise + } + + this.initializationPromise = this._initialize() + return this.initializationPromise + } + + async _initialize () { + try { + // Initialize wallet + this.wallet = new SlpWallet(undefined, { + restURL: this.config.restURL, + interface: 'rest-api' + }) + + // Wait for wallet to initialize + await this.wallet.walletInfoPromise + + // Initialize SlpTokenMedia + this.slpTokenMedia = new SlpTokenMedia({ + wallet: this.wallet, + ipfsGatewayUrl: this.config.ipfsGateway + }) + + this.walletInitialized = true + wlogger.info('SLP wallet and token media initialized') + } catch (err) { + wlogger.error('Error initializing SLP wallet:', err) + throw err + } + } + + async getStatus () { + try { + return await this.slpIndexer.get('slp/status/') + } catch (err) { + wlogger.error('Error in SlpUseCases.getStatus()', err) + throw err + } + } + + async getAddress ({ address }) { + try { + return await this.slpIndexer.post('slp/address/', { address }) + } catch (err) { + wlogger.error('Error in SlpUseCases.getAddress()', err) + throw err + } + } + + async getTxid ({ txid }) { + try { + return await this.slpIndexer.post('slp/tx/', { txid }) + } catch (err) { + wlogger.error('Error in SlpUseCases.getTxid()', err) + throw err + } + } + + async getTokenStats ({ tokenId, withTxHistory = false }) { + try { + return await this.slpIndexer.post('slp/token/', { tokenId, withTxHistory }) + } catch (err) { + wlogger.error('Error in SlpUseCases.getTokenStats()', err) + throw err + } + } + + async getTokenData ({ tokenId, withTxHistory = false }) { + try { + const tokenData = {} + + // Get token stats from the Genesis TX of the token + const response = await this.slpIndexer.post('slp/token/', { tokenId, withTxHistory }) + const tokenStats = response.tokenData + + tokenData.genesisData = tokenStats + + // Try to get immutable data + try { + const immutableData = tokenStats.documentUri + tokenData.immutableData = immutableData || '' + } catch (error) { + tokenData.immutableData = '' + } + + // Try to get mutable data + try { + const mutableData = await this.getMutableCid({ tokenStats }) + tokenData.mutableData = mutableData || '' + } catch (error) { + wlogger.warn('Error getting mutable data:', error) + tokenData.mutableData = '' + } + + return tokenData + } catch (err) { + wlogger.error('Error in SlpUseCases.getTokenData()', err) + throw err + } + } + + async getMutableCid ({ tokenStats }) { + // Validate input - this should throw, not be caught + if (!tokenStats || !tokenStats.documentHash) { + throw new Error('No documentHash property found in tokenStats') + } + + try { + await this._ensureInitialized() + + // Get the OP_RETURN data and decode it + const mutableData = await this.decodeOpReturn({ txid: tokenStats.documentHash }) + const jsonData = JSON.parse(mutableData) + + // mda = mutable data address + const mda = jsonData.mda + + // Get the mda transaction history + const transactions = await this.wallet.getTransactions(mda) + wlogger.info(`MDA has ${transactions.length} transactions in its history.`) + + const mdaTxs = transactions + + let data = false + + // These are used to filter blockchain data to find the most recent + // update to the MDA + let largestBlock = 700000 + let largestTimestamp = 1666107111271 + let bestEntry + + // Used to track the number of transactions before the best candidate is found + let txCnt = 0 + + // Map each transaction of the mda + // If it finds an OP_RETURN, decode it and exit the loop + for (let i = 0; i < mdaTxs.length; i++) { + const tx = mdaTxs[i] + const txid = tx.tx_hash + txCnt++ + + data = await this.decodeOpReturn({ txid }) + + // Try parse the OP_RETURN data to a JSON object + if (data) { + try { + // Convert the OP_RETURN data to a JSON object + const obj = JSON.parse(data) + + // Keep searching if this TX does not have a cid value + if (!obj.cid) continue + + // Ensure data was generated by the MDA + const txData = await this.wallet.getTxData([txid]) + const vinAddress = txData[0].vin[0].address + + // Skip entry if it was not made by the MDA private key + if (mda !== vinAddress) { + continue + } + + // First best entry found + if (!bestEntry) { + bestEntry = data + largestBlock = tx.height + + if (obj.ts) { + largestTimestamp = obj.ts + } + } else { + // One candidate already found. Looking for potentially better entry + + if (tx.height < largestBlock) { + // Exit loop if next candidate has an older block height + break + } + + if (obj.ts && obj.ts < largestTimestamp) { + // Continue looping through entries if the current entry in + // the same block has a smaller timestamp + continue + } + + bestEntry = data + largestBlock = tx.height + if (obj.ts) { + largestTimestamp = obj.ts + } + } + } catch (error) { + continue + } + } + } + + wlogger.info(`${txCnt} transactions reviewed to find mutable data.`) + + if (!bestEntry) { + return false + } + + // Get the CID + const obj = JSON.parse(bestEntry) + const cid = obj.cid + + if (!cid) { + return false + } + + // Assuming that CID starts with ipfs://. Cutting out that prefix + const mutableCid = cid.substring(7) + + return mutableCid + } catch (err) { + console.log('Error in SlpUseCases.getMutableCid()', err) + wlogger.error('Error in SlpUseCases.getMutableCid()', err) + return false + } + } + + async decodeOpReturn ({ txid }) { + try { + if (!txid || typeof txid !== 'string') { + throw new Error('txid must be a string.') + } + + // Get transaction data + console.log('Decoding OP_RETURN for TXID: ', txid) + const txData = await this.bchjs.Electrumx.txData(txid) + // console.log(`TXID ${txid}: ${JSON.stringify(txData, null, 2)}`) + let data = false + + // Map the vout of the transaction in search of an OP_RETURN + for (let i = 0; i < txData.details.vout.length; i++) { + const vout = txData.details.vout[i] + + const script = this.bchjs.Script.toASM( + Buffer.from(vout.scriptPubKey.hex, 'hex') + ).split(' ') + + // Exit on the first OP_RETURN found + if (script[0] === 'OP_RETURN') { + data = Buffer.from(script[1], 'hex').toString('ascii') + break + } + } + + return data + } catch (error) { + console.log('Error in SlpUseCases.decodeOpReturn()', error) + wlogger.error('Error in SlpUseCases.decodeOpReturn()', error) + throw error + } + } + + async getCIDData ({ cid }) { + try { + if (!cid || typeof cid !== 'string') { + throw new Error('cid must be a string.') + } + + // Assuming that CID starts with ipfs://. Cutting out that prefix + const cidWithoutPrefix = cid.substring(7) + + const dataUrl = `https://${cidWithoutPrefix}.ipfs.dweb.link/data.json` + wlogger.info(`Fetching IPFS data from: ${dataUrl}`) + + const response = await axios.get(dataUrl) + + return response.data + } catch (error) { + wlogger.error('Error in SlpUseCases.getCIDData()', error) + throw error + } + } +} + +export default SlpUseCases diff --git a/test/unit/adapters/full-node-rpc-unit.js b/test/unit/adapters/full-node-rpc-unit.js new file mode 100644 index 0000000..42794aa --- /dev/null +++ b/test/unit/adapters/full-node-rpc-unit.js @@ -0,0 +1,122 @@ +/* + Unit tests for FullNodeRPCAdapter. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' +import axios from 'axios' + +import FullNodeRPCAdapter from '../../../src/adapters/full-node-rpc.js' + +describe('#full-node-rpc.js', () => { + let sandbox + let axiosCreateStub + let mockAxiosInstance + + const baseConfig = { + fullNode: { + rpcBaseUrl: 'http://127.0.0.1:8332', + rpcUsername: 'user', + rpcPassword: 'pass', + rpcTimeoutMs: 1000, + rpcRequestIdPrefix: 'test' + } + } + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAxiosInstance = { + post: sandbox.stub() + } + axiosCreateStub = sandbox.stub(axios, 'create').returns(mockAxiosInstance) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should throw if full node config is missing', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new FullNodeRPCAdapter({ config: {} }) + }, /Full node RPC configuration is required/) + }) + + it('should create axios client with provided configuration', () => { + // eslint-disable-next-line no-new + new FullNodeRPCAdapter({ config: baseConfig }) + + assert.isTrue(axiosCreateStub.calledOnce) + const options = axiosCreateStub.getCall(0).args[0] + assert.equal(options.baseURL, baseConfig.fullNode.rpcBaseUrl) + assert.equal(options.timeout, baseConfig.fullNode.rpcTimeoutMs) + assert.deepEqual(options.auth, { + username: baseConfig.fullNode.rpcUsername, + password: baseConfig.fullNode.rpcPassword + }) + }) + }) + + describe('#call()', () => { + it('should call RPC method and return result', async () => { + mockAxiosInstance.post.resolves({ data: { result: 'hash' } }) + const uut = new FullNodeRPCAdapter({ config: baseConfig }) + + const result = await uut.call('getbestblockhash', []) + + assert.equal(result, 'hash') + assert.isTrue(mockAxiosInstance.post.calledOnce) + const [, payload] = mockAxiosInstance.post.getCall(0).args + assert.deepEqual(payload, { + jsonrpc: '1.0', + id: 'test-getbestblockhash', + method: 'getbestblockhash', + params: [] + }) + }) + + it('should use custom request id when provided', async () => { + mockAxiosInstance.post.resolves({ data: { result: 123 } }) + const uut = new FullNodeRPCAdapter({ config: baseConfig }) + + await uut.call('getblockcount', [], 'custom-id') + + const [, payload] = mockAxiosInstance.post.getCall(0).args + assert.equal(payload.id, 'custom-id') + }) + + it('should throw formatted error when RPC returns error', async () => { + mockAxiosInstance.post.resolves({ + data: { + error: { message: 'RPC error' } + } + }) + const uut = new FullNodeRPCAdapter({ config: baseConfig }) + + try { + await uut.call('failing', []) + assert.fail('Unexpected success') + } catch (err) { + assert.equal(err.message, 'RPC error') + assert.equal(err.status, 400) + } + }) + + it('should translate network errors into 503 status', async () => { + mockAxiosInstance.post.rejects(new Error('ENOTFOUND fullnode')) + const uut = new FullNodeRPCAdapter({ config: baseConfig }) + + try { + await uut.call('getblockcount', []) + assert.fail('Unexpected success') + } catch (err) { + assert.equal( + err.message, + 'Network error: Could not communicate with full node or other external service.' + ) + assert.equal(err.status, 503) + } + }) + }) +}) diff --git a/test/unit/bin/server-unit.js b/test/unit/bin/server-unit.js new file mode 100644 index 0000000..3977070 --- /dev/null +++ b/test/unit/bin/server-unit.js @@ -0,0 +1,63 @@ +/* + Unit tests for Server class. + Note: Full server testing requires integration tests due to ES module limitations. + These tests focus on testable logic. +*/ + +// npm libraries +import { assert } from 'chai' +import sinon from 'sinon' + +// Unit under test +import Server from '../../../bin/server.js' + +describe('#server.js', () => { + let sandbox + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + uut = new Server() + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#startServer()', () => { + // Note: Full server startup testing requires integration tests + // due to ES module import limitations with Express and Controllers + it('should have startServer method', () => { + assert.isFunction(uut.startServer) + }) + + it('should have controllers property', () => { + assert.property(uut, 'controllers') + }) + + it('should have config property', () => { + assert.property(uut, 'config') + }) + }) + + describe('#sleep()', () => { + it('should sleep for specified milliseconds', async () => { + const start = Date.now() + await uut.sleep(50) + const end = Date.now() + + // Should have slept at least 50ms (allowing some margin) + assert.isAtLeast(end - start, 40) + }) + }) + + describe('#constructor()', () => { + it('should initialize with controllers and config', () => { + const server = new Server() + + assert.property(server, 'controllers') + assert.property(server, 'config') + assert.property(server, 'process') + }) + }) +}) diff --git a/test/unit/controllers/blockchain-controller-unit.js b/test/unit/controllers/blockchain-controller-unit.js new file mode 100644 index 0000000..af25c65 --- /dev/null +++ b/test/unit/controllers/blockchain-controller-unit.js @@ -0,0 +1,214 @@ +/* + Unit tests for BlockchainRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import BlockchainRESTController from '../../../src/controllers/rest-api/full-node/blockchain/controller.js' +import { + createMockRequest, + createMockResponse +} from '../mocks/controller-mocks.js' + +describe('#blockchain-controller.js', () => { + let sandbox + let mockUseCases + let mockAdapters + let uut + + const createBlockchainUseCaseStubs = () => ({ + getBestBlockHash: sandbox.stub().resolves('hash'), + getBlockchainInfo: sandbox.stub().resolves({}), + getBlockCount: sandbox.stub().resolves(123), + getBlockHeader: sandbox.stub().resolves({ header: true }), + getBlockHeaders: sandbox.stub().resolves(['header']), + getChainTips: sandbox.stub().resolves(['tip']), + getDifficulty: sandbox.stub().resolves(1), + getMempoolEntry: sandbox.stub().resolves({}), + getMempoolEntries: sandbox.stub().resolves([]), + getMempoolAncestors: sandbox.stub().resolves([]), + getMempoolInfo: sandbox.stub().resolves({ size: 1 }), + getRawMempool: sandbox.stub().resolves(['tx']), + getTxOut: sandbox.stub().resolves({ value: 1 }), + getTxOutProof: sandbox.stub().resolves('proof'), + getTxOutProofs: sandbox.stub().resolves(['proof']), + verifyTxOutProof: sandbox.stub().resolves(['txid']), + verifyTxOutProofs: sandbox.stub().resolves([['txid']]), + getBlock: sandbox.stub().resolves({ hash: 'abc' }), + getBlockHash: sandbox.stub().resolves('blockhash') + }) + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = { + fullNode: { + validateArraySize: sandbox.stub().returns(true) + } + } + mockUseCases = { + blockchain: createBlockchainUseCaseStubs() + } + uut = new BlockchainRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new BlockchainRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require blockchain use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new BlockchainRESTController({ adapters: mockAdapters, useCases: {} }) + }, /Blockchain use cases required/) + }) + }) + + describe('#root()', () => { + it('should return service status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'blockchain' }) + }) + }) + + describe('#getBestBlockHash()', () => { + it('should return hash on success', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getBestBlockHash(req, res) + + assert.equal(res.statusValue, 200) + assert.equal(res.jsonData, 'hash') + assert.isTrue(mockUseCases.blockchain.getBestBlockHash.calledOnce) + }) + + it('should handle errors via handleError()', async () => { + const error = new Error('failure') + error.status = 422 + mockUseCases.blockchain.getBestBlockHash.rejects(error) + const req = createMockRequest() + const res = createMockResponse() + + await uut.getBestBlockHash(req, res) + + assert.equal(res.statusValue, 422) + assert.deepEqual(res.jsonData, { error: 'failure' }) + }) + }) + + describe('#getBlockHeaderSingle()', () => { + it('should return 400 if hash is missing', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getBlockHeaderSingle(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + + it('should call use case with verbose flag', async () => { + const hash = 'a'.repeat(64) + const req = createMockRequest({ + params: { hash }, + query: { verbose: 'true' } + }) + const res = createMockResponse() + + await uut.getBlockHeaderSingle(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue( + mockUseCases.blockchain.getBlockHeader.calledOnceWithExactly({ + hash, + verbose: true + }) + ) + }) + }) + + describe('#getBlockHeaderBulk()', () => { + it('should return error if hashes is not array', async () => { + const req = createMockRequest({ + body: { hashes: 'not-an-array' }, + locals: {} + }) + const res = createMockResponse() + + await uut.getBlockHeaderBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.include(res.jsonData.error, 'hashes needs to be an array') + }) + + it('should validate array size and call use case', async () => { + const hash = 'a'.repeat(64) + const req = createMockRequest({ + body: { hashes: [hash], verbose: true } + }) + const res = createMockResponse() + mockUseCases.blockchain.getBlockHeaders.resolves(['result']) + + await uut.getBlockHeaderBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, ['result']) + assert.isTrue( + mockAdapters.fullNode.validateArraySize.calledOnceWithExactly(1) + ) + assert.isTrue( + mockUseCases.blockchain.getBlockHeaders.calledOnceWithExactly({ + hashes: [hash], + verbose: true + }) + ) + }) + + it('should return error if array size invalid', async () => { + mockAdapters.fullNode.validateArraySize.returns(false) + const req = createMockRequest({ + body: { hashes: ['a'.repeat(64)] }, + locals: {} + }) + const res = createMockResponse() + + await uut.getBlockHeaderBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.equal(res.jsonData.error, 'Array too large.') + }) + }) + + describe('#verifyTxOutProofBulk()', () => { + it('should flatten proof responses', async () => { + mockUseCases.blockchain.verifyTxOutProofs.resolves([['txid-a'], ['txid-b']]) + const req = createMockRequest({ + body: { proofs: ['proof-a', 'proof-b'] }, + locals: {} + }) + const res = createMockResponse() + + await uut.verifyTxOutProofBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, ['txid-a', 'txid-b']) + }) + }) +}) diff --git a/test/unit/controllers/control-controller-unit.js b/test/unit/controllers/control-controller-unit.js new file mode 100644 index 0000000..a933357 --- /dev/null +++ b/test/unit/controllers/control-controller-unit.js @@ -0,0 +1,88 @@ +/* + Unit tests for ControlRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import ControlRESTController from '../../../src/controllers/rest-api/full-node/control/controller.js' +import { createMockRequest, createMockResponse } from '../mocks/controller-mocks.js' + +describe('#control-controller.js', () => { + let sandbox + let mockAdapters + let mockUseCases + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = {} + mockUseCases = { + control: { + getNetworkInfo: sandbox.stub().resolves({ version: 1 }) + } + } + + uut = new ControlRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new ControlRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require control use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new ControlRESTController({ adapters: mockAdapters, useCases: {} }) + }, /Control use cases required/) + }) + }) + + describe('#root()', () => { + it('should return control status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'control' }) + }) + }) + + describe('#getNetworkInfo()', () => { + it('should return network info on success', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getNetworkInfo(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { version: 1 }) + }) + + it('should handle errors via handleError', async () => { + const error = new Error('failure') + error.status = 503 + mockUseCases.control.getNetworkInfo.rejects(error) + const req = createMockRequest() + const res = createMockResponse() + + await uut.getNetworkInfo(req, res) + + assert.equal(res.statusValue, 503) + assert.deepEqual(res.jsonData, { error: 'failure' }) + }) + }) +}) diff --git a/test/unit/controllers/dsproof-controller-unit.js b/test/unit/controllers/dsproof-controller-unit.js new file mode 100644 index 0000000..2353ffc --- /dev/null +++ b/test/unit/controllers/dsproof-controller-unit.js @@ -0,0 +1,117 @@ +/* + Unit tests for DSProofRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import DSProofRESTController from '../../../src/controllers/rest-api/full-node/dsproof/controller.js' +import { createMockRequest, createMockResponse } from '../mocks/controller-mocks.js' + +describe('#dsproof-controller.js', () => { + let sandbox + let mockAdapters + let mockUseCases + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = {} + mockUseCases = { + dsproof: { + getDSProof: sandbox.stub().resolves({ proof: true }) + } + } + + uut = new DSProofRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new DSProofRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require dsproof use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new DSProofRESTController({ adapters: mockAdapters, useCases: {} }) + }, /DSProof use cases required/) + }) + }) + + describe('#root()', () => { + it('should return dsproof status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'dsproof' }) + }) + }) + + describe('#getDSProof()', () => { + it('should validate txid presence', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getDSProof(req, res) + + assert.equal(res.statusValue, 400) + assert.include(res.jsonData.error, 'txid can not be empty') + }) + + it('should validate txid length', async () => { + const req = createMockRequest({ params: { txid: 'abc' } }) + const res = createMockResponse() + + await uut.getDSProof(req, res) + + assert.equal(res.statusValue, 400) + assert.include(res.jsonData.error, 'txid must be of length 64') + }) + + it('should call use case with derived verbose when valid', async () => { + const txid = 'a'.repeat(64) + const req = createMockRequest({ + params: { txid }, + query: { verbose: 'true' } + }) + const res = createMockResponse() + + await uut.getDSProof(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockUseCases.dsproof.getDSProof.calledOnceWithExactly({ + txid, + verbose: 3 + })) + assert.deepEqual(res.jsonData, { proof: true }) + }) + + it('should handle errors via handleError', async () => { + const txid = 'a'.repeat(64) + const error = new Error('failure') + error.status = 422 + mockUseCases.dsproof.getDSProof.rejects(error) + const req = createMockRequest({ params: { txid } }) + const res = createMockResponse() + + await uut.getDSProof(req, res) + + assert.equal(res.statusValue, 422) + assert.deepEqual(res.jsonData, { error: 'failure' }) + }) + }) +}) diff --git a/test/unit/controllers/encryption-controller-unit.js b/test/unit/controllers/encryption-controller-unit.js new file mode 100644 index 0000000..ccad9d7 --- /dev/null +++ b/test/unit/controllers/encryption-controller-unit.js @@ -0,0 +1,203 @@ +/* + Unit tests for EncryptionRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import EncryptionRESTController from '../../../src/controllers/rest-api/encryption/controller.js' +import { createMockRequest, createMockResponse, createMockRequestWithParams } from '../mocks/controller-mocks.js' + +describe('#encryption-controller.js', () => { + let sandbox + let mockAdapters + let mockUseCases + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = {} + mockUseCases = { + encryption: { + getPublicKey: sandbox.stub().resolves({ + success: true, + publicKey: '02abc123def456789' + }) + } + } + + uut = new EncryptionRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new EncryptionRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require encryption use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new EncryptionRESTController({ adapters: mockAdapters, useCases: {} }) + }, /Encryption use cases required/) + }) + }) + + describe('#root()', () => { + it('should return encryption status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'encryption' }) + }) + }) + + describe('#getPublicKey()', () => { + it('should return public key on success', async () => { + const req = createMockRequestWithParams({ + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + }) + const res = createMockResponse() + + await uut.getPublicKey(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { + success: true, + publicKey: '02abc123def456789' + }) + assert.isTrue(mockUseCases.encryption.getPublicKey.calledOnce) + assert.isTrue(mockUseCases.encryption.getPublicKey.calledWith({ + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + })) + }) + + it('should return not found when public key is not found', async () => { + mockUseCases.encryption.getPublicKey.resolves({ + success: false, + publicKey: 'not found' + }) + + const req = createMockRequestWithParams({ + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + }) + const res = createMockResponse() + + await uut.getPublicKey(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { + success: false, + publicKey: 'not found' + }) + }) + + it('should reject array addresses', async () => { + const req = createMockRequestWithParams({ + address: ['addr1', 'addr2'] + }) + const res = createMockResponse() + + await uut.getPublicKey(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { + success: false, + error: 'address can not be an array.' + }) + assert.isFalse(mockUseCases.encryption.getPublicKey.called) + }) + + it('should reject missing address', async () => { + const req = createMockRequestWithParams({}) + const res = createMockResponse() + + await uut.getPublicKey(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { + success: false, + error: 'address is required.' + }) + assert.isFalse(mockUseCases.encryption.getPublicKey.called) + }) + + it('should handle errors via handleError', async () => { + const error = new Error('No transaction history.') + error.status = 400 + mockUseCases.encryption.getPublicKey.rejects(error) + + const req = createMockRequestWithParams({ + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + }) + const res = createMockResponse() + + await uut.getPublicKey(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { + success: false, + error: 'No transaction history.' + }) + }) + + it('should default to 500 status for errors without status', async () => { + const error = new Error('Internal error') + mockUseCases.encryption.getPublicKey.rejects(error) + + const req = createMockRequestWithParams({ + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + }) + const res = createMockResponse() + + await uut.getPublicKey(req, res) + + assert.equal(res.statusValue, 500) + assert.deepEqual(res.jsonData, { + success: false, + error: 'Internal error' + }) + }) + }) + + describe('#handleError()', () => { + it('should use error status and message when provided', () => { + const error = new Error('Custom error') + error.status = 422 + const res = createMockResponse() + + uut.handleError(error, res) + + assert.equal(res.statusValue, 422) + assert.deepEqual(res.jsonData, { + success: false, + error: 'Custom error' + }) + }) + + it('should default to 500 and Internal server error', () => { + const error = {} + const res = createMockResponse() + + uut.handleError(error, res) + + assert.equal(res.statusValue, 500) + assert.deepEqual(res.jsonData, { + success: false, + error: 'Internal server error' + }) + }) + }) +}) diff --git a/test/unit/controllers/fulcrum-controller-unit.js b/test/unit/controllers/fulcrum-controller-unit.js new file mode 100644 index 0000000..d39b1d1 --- /dev/null +++ b/test/unit/controllers/fulcrum-controller-unit.js @@ -0,0 +1,481 @@ +/* + Unit tests for FulcrumRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import FulcrumRESTController from '../../../src/controllers/rest-api/fulcrum/controller.js' +import { + createMockRequest, + createMockResponse +} from '../mocks/controller-mocks.js' + +// Valid mainnet cash address for testing +const VALID_MAINNET_ADDRESS = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + +describe('#fulcrum-controller.js', () => { + let sandbox + let mockUseCases + let mockAdapters + let uut + + const createFulcrumUseCaseStubs = () => ({ + getBalance: sandbox.stub().resolves({ balance: 1000 }), + getBalances: sandbox.stub().resolves({ balances: [] }), + getUtxos: sandbox.stub().resolves({ utxos: [] }), + getUtxosBulk: sandbox.stub().resolves({ utxos: [] }), + getTransactionDetails: sandbox.stub().resolves({ txid: 'abc' }), + getTransactionDetailsBulk: sandbox.stub().resolves({ transactions: [] }), + broadcastTransaction: sandbox.stub().resolves({ txid: 'abc' }), + getBlockHeaders: sandbox.stub().resolves({ headers: [] }), + getBlockHeadersBulk: sandbox.stub().resolves({ headers: [] }), + getTransactions: sandbox.stub().resolves({ transactions: [] }), + getTransactionsBulk: sandbox.stub().resolves({ transactions: [] }), + getMempool: sandbox.stub().resolves({ mempool: [] }), + getMempoolBulk: sandbox.stub().resolves({ mempool: [] }) + }) + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = { + fullNode: { + validateArraySize: sandbox.stub().returns(true) + } + } + mockUseCases = { + fulcrum: createFulcrumUseCaseStubs() + } + + uut = new FulcrumRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new FulcrumRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require fulcrum use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new FulcrumRESTController({ adapters: mockAdapters, useCases: {} }) + }, /Fulcrum use cases required/) + }) + }) + + describe('#root()', () => { + it('should return service status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'fulcrum' }) + }) + }) + + describe('#getBalance()', () => { + it('should return balance on success', async () => { + const req = createMockRequest({ + params: { address: VALID_MAINNET_ADDRESS } + }) + const res = createMockResponse() + + await uut.getBalance(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { balance: 1000 }) + assert.isTrue(mockUseCases.fulcrum.getBalance.calledOnce) + }) + + it('should return error if address is array', async () => { + const req = createMockRequest({ + params: { address: [] } + }) + const res = createMockResponse() + + await uut.getBalance(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + + it('should handle errors via handleError', async () => { + const error = new Error('failure') + error.status = 503 + mockUseCases.fulcrum.getBalance.rejects(error) + const req = createMockRequest({ + params: { address: VALID_MAINNET_ADDRESS } + }) + const res = createMockResponse() + + await uut.getBalance(req, res) + + assert.equal(res.statusValue, 503) + assert.deepEqual(res.jsonData, { error: 'failure' }) + }) + }) + + describe('#balanceBulk()', () => { + it('should return error if addresses is not array', async () => { + const req = createMockRequest({ + body: { addresses: 'not-an-array' } + }) + const res = createMockResponse() + + await uut.balanceBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + + it('should validate array size and call use case', async () => { + const req = createMockRequest({ + body: { addresses: [VALID_MAINNET_ADDRESS] } + }) + const res = createMockResponse() + + await uut.balanceBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockAdapters.fullNode.validateArraySize.calledOnce) + assert.isTrue(mockUseCases.fulcrum.getBalances.calledOnce) + }) + + it('should return error if array size invalid', async () => { + mockAdapters.fullNode.validateArraySize.returns(false) + const req = createMockRequest({ + body: { addresses: [VALID_MAINNET_ADDRESS] } + }) + const res = createMockResponse() + + await uut.balanceBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.equal(res.jsonData.error, 'Array too large.') + }) + }) + + describe('#getUtxos()', () => { + it('should return utxos on success', async () => { + const req = createMockRequest({ + params: { address: VALID_MAINNET_ADDRESS } + }) + const res = createMockResponse() + + await uut.getUtxos(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { utxos: [] }) + assert.isTrue(mockUseCases.fulcrum.getUtxos.calledOnce) + }) + }) + + describe('#utxosBulk()', () => { + it('should validate array and call use case', async () => { + const req = createMockRequest({ + body: { addresses: [VALID_MAINNET_ADDRESS] } + }) + const res = createMockResponse() + + await uut.utxosBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockUseCases.fulcrum.getUtxosBulk.calledOnce) + }) + }) + + describe('#getTransactionDetails()', () => { + it('should return transaction details on success', async () => { + const txid = 'a'.repeat(64) + const req = createMockRequest({ + params: { txid } + }) + const res = createMockResponse() + + await uut.getTransactionDetails(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { txid: 'abc' }) + assert.isTrue(mockUseCases.fulcrum.getTransactionDetails.calledOnce) + }) + + it('should return error if txid is not string', async () => { + const req = createMockRequest({ + params: { txid: 123 } + }) + const res = createMockResponse() + + await uut.getTransactionDetails(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + }) + + describe('#transactionDetailsBulk()', () => { + it('should validate array and call use case', async () => { + const req = createMockRequest({ + body: { txids: ['a'.repeat(64)], verbose: true } + }) + const res = createMockResponse() + + await uut.transactionDetailsBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockUseCases.fulcrum.getTransactionDetailsBulk.calledOnce) + }) + + it('should default verbose to true', async () => { + const req = createMockRequest({ + body: { txids: ['a'.repeat(64)] } + }) + const res = createMockResponse() + + await uut.transactionDetailsBulk(req, res) + + assert.isTrue( + mockUseCases.fulcrum.getTransactionDetailsBulk.calledWithMatch({ + txids: ['a'.repeat(64)], + verbose: true + }) + ) + }) + }) + + describe('#broadcastTransaction()', () => { + it('should broadcast transaction on success', async () => { + const req = createMockRequest({ + body: { txHex: '010203' } + }) + const res = createMockResponse() + + await uut.broadcastTransaction(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { txid: 'abc' }) + assert.isTrue(mockUseCases.fulcrum.broadcastTransaction.calledOnce) + }) + + it('should return error if txHex is not string', async () => { + const req = createMockRequest({ + body: { txHex: 123 } + }) + const res = createMockResponse() + + await uut.broadcastTransaction(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + }) + + describe('#getBlockHeaders()', () => { + it('should return block headers on success', async () => { + const req = createMockRequest({ + params: { height: '100' }, + query: { count: '2' } + }) + const res = createMockResponse() + + await uut.getBlockHeaders(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue( + mockUseCases.fulcrum.getBlockHeaders.calledWithMatch({ + height: 100, + count: 2 + }) + ) + }) + + it('should default count to 1', async () => { + const req = createMockRequest({ + params: { height: '100' } + }) + const res = createMockResponse() + + await uut.getBlockHeaders(req, res) + + assert.isTrue( + mockUseCases.fulcrum.getBlockHeaders.calledWithMatch({ + height: 100, + count: 1 + }) + ) + }) + + it('should return error if height is invalid', async () => { + const req = createMockRequest({ + params: { height: 'invalid' } + }) + const res = createMockResponse() + + await uut.getBlockHeaders(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + }) + + describe('#blockHeadersBulk()', () => { + it('should validate heights array and call use case', async () => { + const req = createMockRequest({ + body: { heights: [{ height: 100, count: 2 }] } + }) + const res = createMockResponse() + + await uut.blockHeadersBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockUseCases.fulcrum.getBlockHeadersBulk.calledOnce) + }) + + it('should return error if heights is not array', async () => { + const req = createMockRequest({ + body: { heights: 'not-an-array' } + }) + const res = createMockResponse() + + await uut.blockHeadersBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + + it('should validate height objects', async () => { + const req = createMockRequest({ + body: { heights: [{ height: 'invalid', count: 2 }] } + }) + const res = createMockResponse() + + await uut.blockHeadersBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + }) + + describe('#getTransactions()', () => { + it('should return transactions on success', async () => { + const req = createMockRequest({ + params: { address: VALID_MAINNET_ADDRESS } + }) + const res = createMockResponse() + + await uut.getTransactions(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockUseCases.fulcrum.getTransactions.calledOnce) + }) + + it('should handle allTxs from params', async () => { + const req = createMockRequest({ + params: { address: VALID_MAINNET_ADDRESS, allTxs: 'true' } + }) + const res = createMockResponse() + + await uut.getTransactions(req, res) + + assert.isTrue( + mockUseCases.fulcrum.getTransactions.calledWithMatch({ + address: VALID_MAINNET_ADDRESS, + allTxs: true + }) + ) + }) + + it('should handle allTxs from query', async () => { + const req = createMockRequest({ + params: { address: VALID_MAINNET_ADDRESS }, + query: { allTxs: 'true' } + }) + const res = createMockResponse() + + await uut.getTransactions(req, res) + + assert.isTrue( + mockUseCases.fulcrum.getTransactions.calledWithMatch({ + allTxs: true + }) + ) + }) + }) + + describe('#transactionsBulk()', () => { + it('should validate addresses and call use case', async () => { + const req = createMockRequest({ + body: { addresses: [VALID_MAINNET_ADDRESS], allTxs: true } + }) + const res = createMockResponse() + + await uut.transactionsBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockUseCases.fulcrum.getTransactionsBulk.calledOnce) + }) + }) + + describe('#getMempool()', () => { + it('should return mempool on success', async () => { + const req = createMockRequest({ + params: { address: VALID_MAINNET_ADDRESS } + }) + const res = createMockResponse() + + await uut.getMempool(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { mempool: [] }) + assert.isTrue(mockUseCases.fulcrum.getMempool.calledOnce) + }) + }) + + describe('#mempoolBulk()', () => { + it('should validate addresses and call use case', async () => { + const req = createMockRequest({ + body: { addresses: [VALID_MAINNET_ADDRESS] } + }) + const res = createMockResponse() + + await uut.mempoolBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockUseCases.fulcrum.getMempoolBulk.calledOnce) + }) + }) + + describe('#handleError()', () => { + it('should handle errors with status', async () => { + const error = new Error('test error') + error.status = 400 + const res = createMockResponse() + + uut.handleError(error, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'test error' }) + }) + + it('should default status to 500', async () => { + const error = new Error('test error') + const res = createMockResponse() + + uut.handleError(error, res) + + assert.equal(res.statusValue, 500) + assert.deepEqual(res.jsonData, { error: 'test error' }) + }) + }) +}) diff --git a/test/unit/controllers/mining-controller-unit.js b/test/unit/controllers/mining-controller-unit.js new file mode 100644 index 0000000..bdb4df3 --- /dev/null +++ b/test/unit/controllers/mining-controller-unit.js @@ -0,0 +1,139 @@ +/* + Unit tests for MiningRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import MiningRESTController from '../../../src/controllers/rest-api/full-node/mining/controller.js' +import { createMockRequest, createMockResponse } from '../mocks/controller-mocks.js' + +describe('#mining-controller.js', () => { + let sandbox + let mockAdapters + let mockUseCases + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = {} + mockUseCases = { + mining: { + getMiningInfo: sandbox.stub().resolves({ blocks: 100, difficulty: 1.5 }), + getNetworkHashPS: sandbox.stub().resolves(1234567890) + } + } + + uut = new MiningRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new MiningRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require mining use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new MiningRESTController({ adapters: mockAdapters, useCases: {} }) + }, /Mining use cases required/) + }) + }) + + describe('#root()', () => { + it('should return mining status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'mining' }) + }) + }) + + describe('#getMiningInfo()', () => { + it('should return mining info on success', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getMiningInfo(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { blocks: 100, difficulty: 1.5 }) + assert.isTrue(mockUseCases.mining.getMiningInfo.calledOnce) + }) + + it('should handle errors via handleError', async () => { + const error = new Error('failure') + error.status = 503 + mockUseCases.mining.getMiningInfo.rejects(error) + const req = createMockRequest() + const res = createMockResponse() + + await uut.getMiningInfo(req, res) + + assert.equal(res.statusValue, 503) + assert.deepEqual(res.jsonData, { error: 'failure' }) + }) + }) + + describe('#getNetworkHashPS()', () => { + it('should return network hash PS with default params', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getNetworkHashPS(req, res) + + assert.equal(res.statusValue, 200) + assert.equal(res.jsonData, 1234567890) + assert.isTrue(mockUseCases.mining.getNetworkHashPS.calledOnce) + assert.deepEqual(mockUseCases.mining.getNetworkHashPS.firstCall.args[0], { + nblocks: 120, + height: -1 + }) + }) + + it('should parse query params for nblocks and height', async () => { + const req = createMockRequest({ + query: { + nblocks: '240', + height: '1000' + } + }) + const res = createMockResponse() + + await uut.getNetworkHashPS(req, res) + + assert.equal(res.statusValue, 200) + assert.isTrue(mockUseCases.mining.getNetworkHashPS.calledOnce) + assert.deepEqual(mockUseCases.mining.getNetworkHashPS.firstCall.args[0], { + nblocks: 240, + height: 1000 + }) + }) + + it('should handle errors via handleError', async () => { + const error = new Error('RPC error') + error.status = 500 + mockUseCases.mining.getNetworkHashPS.rejects(error) + const req = createMockRequest() + const res = createMockResponse() + + await uut.getNetworkHashPS(req, res) + + assert.equal(res.statusValue, 500) + assert.deepEqual(res.jsonData, { error: 'RPC error' }) + }) + }) +}) diff --git a/test/unit/controllers/price-controller-unit.js b/test/unit/controllers/price-controller-unit.js new file mode 100644 index 0000000..68e046f --- /dev/null +++ b/test/unit/controllers/price-controller-unit.js @@ -0,0 +1,116 @@ +/* + Unit tests for PriceRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import PriceRESTController from '../../../src/controllers/rest-api/price/controller.js' +import { createMockRequest, createMockResponse } from '../mocks/controller-mocks.js' + +describe('#price-controller.js', () => { + let sandbox + let mockAdapters + let mockUseCases + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = {} + mockUseCases = { + price: { + getBCHUSD: sandbox.stub().resolves(250.5), + getPsffppWritePrice: sandbox.stub().resolves(0.08335233) + } + } + + uut = new PriceRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new PriceRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require price use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new PriceRESTController({ adapters: mockAdapters, useCases: {} }) + }, /Price use cases required/) + }) + }) + + describe('#root()', () => { + it('should return price status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'price' }) + }) + }) + + describe('#getBCHUSD()', () => { + it('should return BCH USD price on success', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getBCHUSD(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { usd: 250.5 }) + assert.isTrue(mockUseCases.price.getBCHUSD.calledOnce) + }) + + it('should handle errors via handleError', async () => { + const error = new Error('API failure') + error.status = 503 + mockUseCases.price.getBCHUSD.rejects(error) + const req = createMockRequest() + const res = createMockResponse() + + await uut.getBCHUSD(req, res) + + assert.equal(res.statusValue, 503) + assert.deepEqual(res.jsonData, { error: 'API failure' }) + }) + }) + + describe('#getPsffppWritePrice()', () => { + it('should return PSFFPP write price on success', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getPsffppWritePrice(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { writePrice: 0.08335233 }) + assert.isTrue(mockUseCases.price.getPsffppWritePrice.calledOnce) + }) + + it('should handle errors via handleError', async () => { + const error = new Error('PSFFPP failure') + error.status = 500 + mockUseCases.price.getPsffppWritePrice.rejects(error) + const req = createMockRequest() + const res = createMockResponse() + + await uut.getPsffppWritePrice(req, res) + + assert.equal(res.statusValue, 500) + assert.deepEqual(res.jsonData, { error: 'PSFFPP failure' }) + }) + }) +}) diff --git a/test/unit/controllers/rawtransactions-controller-unit.js b/test/unit/controllers/rawtransactions-controller-unit.js new file mode 100644 index 0000000..fcd9100 --- /dev/null +++ b/test/unit/controllers/rawtransactions-controller-unit.js @@ -0,0 +1,388 @@ +/* + Unit tests for RawTransactionsRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import RawTransactionsRESTController from '../../../src/controllers/rest-api/full-node/rawtransactions/controller.js' +import { createMockRequest, createMockResponse } from '../mocks/controller-mocks.js' + +describe('#rawtransactions-controller.js', () => { + let sandbox + let mockAdapters + let mockUseCases + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = { + fullNode: { + validateArraySize: sandbox.stub().returns(true) + } + } + mockUseCases = { + rawtransactions: { + decodeRawTransaction: sandbox.stub().resolves({ txid: 'abc123' }), + decodeRawTransactions: sandbox.stub().resolves([{ txid: 'abc123' }]), + decodeScript: sandbox.stub().resolves({ asm: 'OP_DUP' }), + decodeScripts: sandbox.stub().resolves([{ asm: 'OP_DUP' }]), + getRawTransaction: sandbox.stub().resolves({ txid: 'abc123' }), + getRawTransactionWithHeight: sandbox.stub().resolves({ txid: 'abc123', height: 100 }), + getRawTransactions: sandbox.stub().resolves([{ txid: 'abc123' }]), + sendRawTransaction: sandbox.stub().resolves('txid123'), + sendRawTransactions: sandbox.stub().resolves(['txid1', 'txid2']) + } + } + + uut = new RawTransactionsRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new RawTransactionsRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require rawtransactions use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new RawTransactionsRESTController({ adapters: mockAdapters, useCases: {} }) + }, /RawTransactions use cases required/) + }) + }) + + describe('#root()', () => { + it('should return rawtransactions status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'rawtransactions' }) + }) + }) + + describe('#decodeRawTransactionSingle()', () => { + it('should return decoded transaction on success', async () => { + const req = createMockRequest({ params: { hex: '01000000' } }) + const res = createMockResponse() + + await uut.decodeRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { txid: 'abc123' }) + assert.isTrue(mockUseCases.rawtransactions.decodeRawTransaction.calledOnce) + assert.deepEqual(mockUseCases.rawtransactions.decodeRawTransaction.firstCall.args[0], { hex: '01000000' }) + }) + + it('should return 400 if hex is empty', async () => { + const req = createMockRequest({ params: { hex: '' } }) + const res = createMockResponse() + + await uut.decodeRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'hex can not be empty' }) + }) + + it('should handle errors via handleError', async () => { + const error = new Error('RPC error') + error.status = 500 + mockUseCases.rawtransactions.decodeRawTransaction.rejects(error) + const req = createMockRequest({ params: { hex: '01000000' } }) + const res = createMockResponse() + + await uut.decodeRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 500) + assert.deepEqual(res.jsonData, { error: 'RPC error' }) + }) + }) + + describe('#decodeRawTransactionBulk()', () => { + it('should return decoded transactions on success', async () => { + const req = createMockRequest({ body: { hexes: ['hex1', 'hex2'] } }) + const res = createMockResponse() + + await uut.decodeRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, [{ txid: 'abc123' }]) + assert.isTrue(mockUseCases.rawtransactions.decodeRawTransactions.calledOnce) + }) + + it('should return 400 if hexes is not an array', async () => { + const req = createMockRequest({ body: { hexes: 'not-array' } }) + const res = createMockResponse() + + await uut.decodeRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'hexes must be an array' }) + }) + + it('should return 400 if array is too large', async () => { + mockAdapters.fullNode.validateArraySize.returns(false) + const req = createMockRequest({ body: { hexes: new Array(25).fill('hex') } }) + const res = createMockResponse() + + await uut.decodeRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Array too large.' }) + }) + + it('should return 400 if empty hex encountered', async () => { + const req = createMockRequest({ body: { hexes: ['hex1', '', 'hex2'] } }) + const res = createMockResponse() + + await uut.decodeRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Encountered empty hex' }) + }) + }) + + describe('#decodeScriptSingle()', () => { + it('should return decoded script on success', async () => { + const req = createMockRequest({ params: { hex: '76a914' } }) + const res = createMockResponse() + + await uut.decodeScriptSingle(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { asm: 'OP_DUP' }) + assert.isTrue(mockUseCases.rawtransactions.decodeScript.calledOnce) + }) + + it('should return 400 if hex is empty', async () => { + const req = createMockRequest({ params: { hex: '' } }) + const res = createMockResponse() + + await uut.decodeScriptSingle(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'hex can not be empty' }) + }) + }) + + describe('#decodeScriptBulk()', () => { + it('should return decoded scripts on success', async () => { + const req = createMockRequest({ body: { hexes: ['script1', 'script2'] } }) + const res = createMockResponse() + + await uut.decodeScriptBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, [{ asm: 'OP_DUP' }]) + }) + + it('should return 400 if array is too large', async () => { + mockAdapters.fullNode.validateArraySize.returns(false) + const req = createMockRequest({ body: { hexes: new Array(25).fill('script') } }) + const res = createMockResponse() + + await uut.decodeScriptBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Array too large.' }) + }) + }) + + describe('#getRawTransactionSingle()', () => { + it('should return raw transaction on success', async () => { + const req = createMockRequest({ params: { txid: 'a'.repeat(64) }, query: {} }) + const res = createMockResponse() + + await uut.getRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { txid: 'abc123', height: 100 }) + assert.isTrue(mockUseCases.rawtransactions.getRawTransactionWithHeight.calledOnce) + }) + + it('should pass verbose=true when query param is set', async () => { + const req = createMockRequest({ params: { txid: 'a'.repeat(64) }, query: { verbose: 'true' } }) + const res = createMockResponse() + + await uut.getRawTransactionSingle(req, res) + + assert.isTrue(mockUseCases.rawtransactions.getRawTransactionWithHeight.calledOnce) + assert.deepEqual(mockUseCases.rawtransactions.getRawTransactionWithHeight.firstCall.args[0], { + txid: 'a'.repeat(64), + verbose: true + }) + }) + + it('should return 400 if txid is empty', async () => { + const req = createMockRequest({ params: { txid: '' } }) + const res = createMockResponse() + + await uut.getRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'txid can not be empty' }) + }) + + it('should return 400 if txid length is not 64', async () => { + const req = createMockRequest({ params: { txid: 'short' } }) + const res = createMockResponse() + + await uut.getRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'parameter 1 must be of length 64 (not 5)' }) + }) + }) + + describe('#getRawTransactionBulk()', () => { + it('should return raw transactions on success', async () => { + const req = createMockRequest({ + body: { + txids: ['a'.repeat(64), 'b'.repeat(64)], + verbose: true + } + }) + const res = createMockResponse() + + await uut.getRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, [{ txid: 'abc123' }]) + assert.isTrue(mockUseCases.rawtransactions.getRawTransactions.calledOnce) + assert.deepEqual(mockUseCases.rawtransactions.getRawTransactions.firstCall.args[0], { + txids: ['a'.repeat(64), 'b'.repeat(64)], + verbose: true + }) + }) + + it('should return 400 if txids is not an array', async () => { + const req = createMockRequest({ body: { txids: 'not-array' } }) + const res = createMockResponse() + + await uut.getRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'txids must be an array' }) + }) + + it('should return 400 if array is too large', async () => { + mockAdapters.fullNode.validateArraySize.returns(false) + const req = createMockRequest({ body: { txids: new Array(25).fill('a'.repeat(64)) } }) + const res = createMockResponse() + + await uut.getRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Array too large.' }) + }) + + it('should return 400 if empty txid encountered', async () => { + const req = createMockRequest({ body: { txids: ['a'.repeat(64), ''] } }) + const res = createMockResponse() + + await uut.getRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Encountered empty TXID' }) + }) + + it('should return 400 if txid length is not 64', async () => { + const req = createMockRequest({ body: { txids: ['short'] } }) + const res = createMockResponse() + + await uut.getRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'parameter 1 must be of length 64 (not 5)' }) + }) + }) + + describe('#sendRawTransactionSingle()', () => { + it('should return txid on success', async () => { + const req = createMockRequest({ params: { hex: '01000000' } }) + const res = createMockResponse() + + await uut.sendRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 200) + assert.equal(res.jsonData, 'txid123') + assert.isTrue(mockUseCases.rawtransactions.sendRawTransaction.calledOnce) + }) + + it('should return 400 if hex is empty', async () => { + const req = createMockRequest({ params: { hex: '' } }) + const res = createMockResponse() + + await uut.sendRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Encountered empty hex' }) + }) + + it('should return 400 if hex is not a string', async () => { + const req = createMockRequest({ params: { hex: 123 } }) + const res = createMockResponse() + + await uut.sendRawTransactionSingle(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'hex must be a string' }) + }) + }) + + describe('#sendRawTransactionBulk()', () => { + it('should return txids on success', async () => { + const req = createMockRequest({ body: { hexes: ['hex1', 'hex2'] } }) + const res = createMockResponse() + + await uut.sendRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, ['txid1', 'txid2']) + assert.isTrue(mockUseCases.rawtransactions.sendRawTransactions.calledOnce) + }) + + it('should return 400 if hexes is not an array', async () => { + const req = createMockRequest({ body: { hexes: 'not-array' } }) + const res = createMockResponse() + + await uut.sendRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'hex must be an array' }) + }) + + it('should return 400 if array is too large', async () => { + mockAdapters.fullNode.validateArraySize.returns(false) + const req = createMockRequest({ body: { hexes: new Array(25).fill('hex') } }) + const res = createMockResponse() + + await uut.sendRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Array too large.' }) + }) + + it('should return 400 if empty hex encountered', async () => { + const req = createMockRequest({ body: { hexes: ['hex1', '', 'hex2'] } }) + const res = createMockResponse() + + await uut.sendRawTransactionBulk(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Encountered empty hex' }) + }) + }) +}) diff --git a/test/unit/controllers/rest-api-index-unit.js b/test/unit/controllers/rest-api-index-unit.js new file mode 100644 index 0000000..3051a16 --- /dev/null +++ b/test/unit/controllers/rest-api-index-unit.js @@ -0,0 +1,170 @@ +/* + Unit tests for RESTControllers index. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import RESTControllers from '../../../src/controllers/rest-api/index.js' +import BlockchainRouter from '../../../src/controllers/rest-api/full-node/blockchain/router.js' +import ControlRouter from '../../../src/controllers/rest-api/full-node/control/router.js' +import DSProofRouter from '../../../src/controllers/rest-api/full-node/dsproof/router.js' +import EncryptionRouter from '../../../src/controllers/rest-api/encryption/router.js' +import MiningRouter from '../../../src/controllers/rest-api/full-node/mining/router.js' +import PriceRouter from '../../../src/controllers/rest-api/price/router.js' +import RawTransactionsRouter from '../../../src/controllers/rest-api/full-node/rawtransactions/router.js' +import FulcrumRouter from '../../../src/controllers/rest-api/fulcrum/router.js' +import SlpRouter from '../../../src/controllers/rest-api/slp/router.js' + +describe('#controllers/rest-api/index.js', () => { + let sandbox + let mockAdapters + let mockUseCases + + const createBlockchainUseCaseStubs = () => ({ + getBestBlockHash: () => {}, + getBlockchainInfo: () => {}, + getBlockCount: () => {}, + getBlockHeader: () => {}, + getBlockHeaders: () => {}, + getChainTips: () => {}, + getDifficulty: () => {}, + getMempoolEntry: () => {}, + getMempoolEntries: () => {}, + getMempoolAncestors: () => {}, + getMempoolInfo: () => {}, + getRawMempool: () => {}, + getTxOut: () => {}, + getTxOutProof: () => {}, + getTxOutProofs: () => {}, + verifyTxOutProof: () => {}, + verifyTxOutProofs: () => {}, + getBlock: () => {}, + getBlockHash: () => {} + }) + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = { + fullNode: { + validateArraySize: sandbox.stub().returns(true) + } + } + mockUseCases = { + blockchain: createBlockchainUseCaseStubs(), + control: { + getNetworkInfo: () => {} + }, + dsproof: { + getDSProof: () => {} + }, + fulcrum: { + getBalance: () => {}, + getBalances: () => {}, + getUtxos: () => {}, + getUtxosBulk: () => {}, + getTransactionDetails: () => {}, + getTransactionDetailsBulk: () => {}, + broadcastTransaction: () => {}, + getBlockHeaders: () => {}, + getBlockHeadersBulk: () => {}, + getTransactions: () => {}, + getTransactionsBulk: () => {}, + getMempool: () => {}, + getMempoolBulk: () => {} + }, + mining: { + getMiningInfo: () => {}, + getNetworkHashPS: () => {} + }, + price: { + getBCHUSD: () => {}, + getPsffppWritePrice: () => {} + }, + rawtransactions: { + decodeRawTransaction: () => {}, + decodeRawTransactions: () => {}, + decodeScript: () => {}, + decodeScripts: () => {}, + getRawTransaction: () => {}, + getRawTransactionWithHeight: () => {}, + getRawTransactions: () => {}, + sendRawTransaction: () => {}, + sendRawTransactions: () => {} + }, + slp: { + getStatus: () => {}, + getAddress: () => {}, + getTxid: () => {}, + getTokenStats: () => {}, + getTokenData: () => {}, + getMutableCid: () => {}, + decodeOpReturn: () => {}, + getCIDData: () => {} + }, + encryption: { + getPublicKey: () => {} + } + } + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters instance', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new RESTControllers({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require useCases instance', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new RESTControllers({ adapters: mockAdapters }) + }, /Use Cases library required/) + }) + }) + + describe('#attachRESTControllers()', () => { + it('should instantiate routers and attach to app', () => { + const blockchainAttachStub = sandbox.stub(BlockchainRouter.prototype, 'attach') + const controlAttachStub = sandbox.stub(ControlRouter.prototype, 'attach') + const dsproofAttachStub = sandbox.stub(DSProofRouter.prototype, 'attach') + const encryptionAttachStub = sandbox.stub(EncryptionRouter.prototype, 'attach') + const fulcrumAttachStub = sandbox.stub(FulcrumRouter.prototype, 'attach') + const miningAttachStub = sandbox.stub(MiningRouter.prototype, 'attach') + const priceAttachStub = sandbox.stub(PriceRouter.prototype, 'attach') + const rawtransactionsAttachStub = sandbox.stub(RawTransactionsRouter.prototype, 'attach') + const slpAttachStub = sandbox.stub(SlpRouter.prototype, 'attach') + const restControllers = new RESTControllers({ + adapters: mockAdapters, + useCases: mockUseCases + }) + const app = {} + + restControllers.attachRESTControllers(app) + + assert.isTrue(blockchainAttachStub.calledOnce) + assert.equal(blockchainAttachStub.getCall(0).args[0], app) + assert.isTrue(controlAttachStub.calledOnce) + assert.equal(controlAttachStub.getCall(0).args[0], app) + assert.isTrue(dsproofAttachStub.calledOnce) + assert.equal(dsproofAttachStub.getCall(0).args[0], app) + assert.isTrue(encryptionAttachStub.calledOnce) + assert.equal(encryptionAttachStub.getCall(0).args[0], app) + assert.isTrue(fulcrumAttachStub.calledOnce) + assert.equal(fulcrumAttachStub.getCall(0).args[0], app) + assert.isTrue(miningAttachStub.calledOnce) + assert.equal(miningAttachStub.getCall(0).args[0], app) + assert.isTrue(priceAttachStub.calledOnce) + assert.equal(priceAttachStub.getCall(0).args[0], app) + assert.isTrue(rawtransactionsAttachStub.calledOnce) + assert.equal(rawtransactionsAttachStub.getCall(0).args[0], app) + assert.isTrue(slpAttachStub.calledOnce) + assert.equal(slpAttachStub.getCall(0).args[0], app) + }) + }) +}) diff --git a/test/unit/controllers/slp-controller-unit.js b/test/unit/controllers/slp-controller-unit.js new file mode 100644 index 0000000..650f791 --- /dev/null +++ b/test/unit/controllers/slp-controller-unit.js @@ -0,0 +1,312 @@ +/* + Unit tests for SlpRESTController. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import SlpRESTController from '../../../src/controllers/rest-api/slp/controller.js' +import { + createMockRequest, + createMockResponse +} from '../mocks/controller-mocks.js' + +// Valid mainnet cash address for testing +const VALID_MAINNET_ADDRESS = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + +describe('#slp-controller.js', () => { + let sandbox + let mockUseCases + let mockAdapters + let uut + + const createSlpUseCaseStubs = () => ({ + getStatus: sandbox.stub().resolves({ status: 'ok' }), + getAddress: sandbox.stub().resolves({ balance: 1000 }), + getTxid: sandbox.stub().resolves({ txid: 'abc' }), + getTokenStats: sandbox.stub().resolves({ tokenData: {} }), + getTokenData: sandbox.stub().resolves({ genesisData: {}, immutableData: '', mutableData: '' }) + }) + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = {} + mockUseCases = { + slp: createSlpUseCaseStubs() + } + + uut = new SlpRESTController({ + adapters: mockAdapters, + useCases: mockUseCases + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new SlpRESTController({ useCases: mockUseCases }) + }, /Adapters library required/) + }) + + it('should require slp use cases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new SlpRESTController({ adapters: mockAdapters, useCases: {} }) + }, /SLP use cases required/) + }) + }) + + describe('#root()', () => { + it('should return service status', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.root(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'psf-slp-indexer' }) + }) + }) + + describe('#getStatus()', () => { + it('should return status on success', async () => { + const req = createMockRequest() + const res = createMockResponse() + + await uut.getStatus(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { status: 'ok' }) + assert.isTrue(mockUseCases.slp.getStatus.calledOnce) + }) + + it('should handle errors via handleError', async () => { + const error = new Error('failure') + error.status = 503 + mockUseCases.slp.getStatus.rejects(error) + const req = createMockRequest() + const res = createMockResponse() + + await uut.getStatus(req, res) + + assert.equal(res.statusValue, 503) + assert.deepEqual(res.jsonData, { error: 'failure' }) + }) + }) + + describe('#getAddress()', () => { + it('should return address balance on success', async () => { + const req = createMockRequest({ + body: { address: VALID_MAINNET_ADDRESS } + }) + const res = createMockResponse() + + await uut.getAddress(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { balance: 1000 }) + assert.isTrue(mockUseCases.slp.getAddress.calledOnce) + }) + + it('should return error if address is empty', async () => { + const req = createMockRequest({ + body: { address: '' } + }) + const res = createMockResponse() + + await uut.getAddress(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + assert.include(res.jsonData.error, 'can not be empty') + }) + + it('should return error if address is missing', async () => { + const req = createMockRequest({ + body: {} + }) + const res = createMockResponse() + + await uut.getAddress(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + + it('should handle errors via handleError', async () => { + const error = new Error('Invalid address') + error.status = 400 + mockUseCases.slp.getAddress.rejects(error) + const req = createMockRequest({ + body: { address: VALID_MAINNET_ADDRESS } + }) + const res = createMockResponse() + + await uut.getAddress(req, res) + + assert.equal(res.statusValue, 400) + assert.deepEqual(res.jsonData, { error: 'Invalid address' }) + }) + }) + + describe('#getTxid()', () => { + it('should return transaction data on success', async () => { + const req = createMockRequest({ + body: { txid: 'a'.repeat(64) } + }) + const res = createMockResponse() + + await uut.getTxid(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { txid: 'abc' }) + assert.isTrue(mockUseCases.slp.getTxid.calledOnce) + }) + + it('should return error if txid is empty', async () => { + const req = createMockRequest({ + body: { txid: '' } + }) + const res = createMockResponse() + + await uut.getTxid(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + assert.include(res.jsonData.error, 'can not be empty') + }) + + it('should return error if txid is not 64 characters', async () => { + const req = createMockRequest({ + body: { txid: 'abc' } + }) + const res = createMockResponse() + + await uut.getTxid(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + assert.include(res.jsonData.error, 'not a txid') + }) + + it('should handle errors via handleError', async () => { + const error = new Error('Transaction not found') + error.status = 404 + mockUseCases.slp.getTxid.rejects(error) + const req = createMockRequest({ + body: { txid: 'a'.repeat(64) } + }) + const res = createMockResponse() + + await uut.getTxid(req, res) + + assert.equal(res.statusValue, 404) + assert.deepEqual(res.jsonData, { error: 'Transaction not found' }) + }) + }) + + describe('#getTokenStats()', () => { + it('should return token stats on success', async () => { + const req = createMockRequest({ + body: { tokenId: 'a'.repeat(64) } + }) + const res = createMockResponse() + + await uut.getTokenStats(req, res) + + assert.equal(res.statusValue, 200) + assert.deepEqual(res.jsonData, { tokenData: {} }) + assert.isTrue(mockUseCases.slp.getTokenStats.calledOnce) + }) + + it('should pass withTxHistory flag', async () => { + const req = createMockRequest({ + body: { tokenId: 'a'.repeat(64), withTxHistory: true } + }) + const res = createMockResponse() + + await uut.getTokenStats(req, res) + + assert.isTrue(mockUseCases.slp.getTokenStats.calledWith({ + tokenId: 'a'.repeat(64), + withTxHistory: true + })) + }) + + it('should return error if tokenId is empty', async () => { + const req = createMockRequest({ + body: { tokenId: '' } + }) + const res = createMockResponse() + + await uut.getTokenStats(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + + it('should handle errors via handleError', async () => { + const error = new Error('Token not found') + error.status = 404 + mockUseCases.slp.getTokenStats.rejects(error) + const req = createMockRequest({ + body: { tokenId: 'a'.repeat(64) } + }) + const res = createMockResponse() + + await uut.getTokenStats(req, res) + + assert.equal(res.statusValue, 404) + assert.deepEqual(res.jsonData, { error: 'Token not found' }) + }) + }) + + describe('#getTokenData()', () => { + it('should return token data on success', async () => { + const req = createMockRequest({ + body: { tokenId: 'a'.repeat(64) } + }) + const res = createMockResponse() + + await uut.getTokenData(req, res) + + assert.equal(res.statusValue, 200) + assert.property(res.jsonData, 'genesisData') + assert.property(res.jsonData, 'immutableData') + assert.property(res.jsonData, 'mutableData') + assert.isTrue(mockUseCases.slp.getTokenData.calledOnce) + }) + + it('should return error if tokenId is empty', async () => { + const req = createMockRequest({ + body: { tokenId: '' } + }) + const res = createMockResponse() + + await uut.getTokenData(req, res) + + assert.equal(res.statusValue, 400) + assert.property(res.jsonData, 'error') + }) + + it('should handle errors via handleError', async () => { + const error = new Error('Token data not found') + error.status = 404 + mockUseCases.slp.getTokenData.rejects(error) + const req = createMockRequest({ + body: { tokenId: 'a'.repeat(64) } + }) + const res = createMockResponse() + + await uut.getTokenData(req, res) + + assert.equal(res.statusValue, 404) + assert.deepEqual(res.jsonData, { error: 'Token data not found' }) + }) + }) +}) diff --git a/test/unit/mocks/controller-mocks.js b/test/unit/mocks/controller-mocks.js new file mode 100644 index 0000000..c114ae2 --- /dev/null +++ b/test/unit/mocks/controller-mocks.js @@ -0,0 +1,98 @@ +/* + Mock Express request/response objects for controller unit tests. +*/ + +// Mock Express request object +export function createMockRequest (overrides = {}) { + return { + body: {}, + params: {}, + query: {}, + method: 'GET', + path: '/', + ...overrides + } +} + +// Mock Express response object +export function createMockResponse () { + const res = { + statusCode: 200, + jsonData: null, + statusValue: null, + headers: {}, + writeData: [], + endCalled: false, + writable: true, // Stream is writable by default + destroyed: false, // Stream is not destroyed by default + closed: false, // Stream is not closed by default + eventHandlers: {} // Store event handlers + } + + res.status = function (code) { + res.statusCode = code + res.statusValue = code + return res + } + + res.json = function (data) { + res.jsonData = data + return res + } + + res.setHeader = function (name, value) { + res.headers[name] = value + return res + } + + res.write = function (data) { + res.writeData.push(data) + return true + } + + res.end = function () { + res.endCalled = true + return res + } + + res.on = function (event, callback) { + // Store event handlers for different event types + if (!res.eventHandlers[event]) { + res.eventHandlers[event] = [] + } + res.eventHandlers[event].push(callback) + + // For backward compatibility with existing tests + if (event === 'close') { + res.closeCallback = callback + } + + return res + } + + // Helper to trigger an event (useful for testing) + res.trigger = function (event, ...args) { + if (res.eventHandlers[event]) { + for (const handler of res.eventHandlers[event]) { + handler(...args) + } + } + } + + return res +} + +// Helper to create a mock request with body +export function createMockRequestWithBody (body) { + return createMockRequest({ body }) +} + +// Helper to create a mock request with params +export function createMockRequestWithParams (params) { + return createMockRequest({ params }) +} + +// Helper to create a mock request with query +export function createMockRequestWithQuery (query) { + return createMockRequest({ query }) +} diff --git a/test/unit/mocks/event-mocks.js b/test/unit/mocks/event-mocks.js new file mode 100644 index 0000000..d7219a6 --- /dev/null +++ b/test/unit/mocks/event-mocks.js @@ -0,0 +1,194 @@ +/* + Mock event data for unit tests. + Contains mock Nostr events for various event kinds. +*/ + +// Alice's public key from examples +const alicePubKey = '2c7e76c0f8dc1dca9d0197c7d19be580a8d074ccada6a2f6ebe056ae41092e92' +const bobPubKey = 'b'.repeat(64) + +// Valid event ID (64 hex chars) +const validEventId = 'd09b4c5da59be3cd2768aa53fa78b77bf4859084c94f3bf26d401f004a9c8167' +// Valid signature (128 hex chars) +const validSig = 'a'.repeat(128) + +// Kind 0: Profile metadata event +const mockKind0Event = { + id: validEventId, + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 0, + tags: [], + content: JSON.stringify({ + name: 'Alice', + about: 'Hello, I am Alice!', + picture: 'https://example.com/alice.jpg' + }), + sig: validSig +} + +// Kind 1: Text post event +const mockKind1Event = { + id: validEventId, + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: 'This is a test message', + sig: validSig +} + +// Kind 3: Follow list event +const mockKind3Event = { + id: validEventId, + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 3, + tags: [ + ['p', bobPubKey, 'wss://nostr-relay.psfoundation.info', 'bob'] + ], + content: '', + sig: validSig +} + +// Kind 7: Reaction/like event +const mockKind7Event = { + id: validEventId, + pubkey: bobPubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 7, + tags: [ + ['e', validEventId, 'wss://nostr-relay.psfoundation.info'], + ['p', alicePubKey, 'wss://nostr-relay.psfoundation.info'] + ], + content: '+', + sig: validSig +} + +// Invalid events for testing validation +const mockInvalidEventMissingId = { + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: 'Test', + sig: validSig +} + +const mockInvalidEventWrongIdLength = { + id: 'short', + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: 'Test', + sig: validSig +} + +const mockInvalidEventMissingPubkey = { + id: validEventId, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: 'Test', + sig: validSig +} + +const mockInvalidEventWrongPubkeyLength = { + id: validEventId, + pubkey: 'short', + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: 'Test', + sig: validSig +} + +const mockInvalidEventMissingCreatedAt = { + id: validEventId, + pubkey: alicePubKey, + kind: 1, + tags: [], + content: 'Test', + sig: validSig +} + +const mockInvalidEventWrongCreatedAtType = { + id: validEventId, + pubkey: alicePubKey, + created_at: 'not-a-number', + kind: 1, + tags: [], + content: 'Test', + sig: validSig +} + +const mockInvalidEventMissingKind = { + id: validEventId, + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + tags: [], + content: 'Test', + sig: validSig +} + +const mockInvalidEventKindOutOfRange = { + id: validEventId, + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 70000, // Out of range (0-65535) + tags: [], + content: 'Test', + sig: validSig +} + +const mockInvalidEventMissingSig = { + id: validEventId, + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: 'Test' +} + +const mockInvalidEventWrongSigLength = { + id: validEventId, + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: 'Test', + sig: 'short' +} + +const mockInvalidEventTagsNotArray = { + id: validEventId, + pubkey: alicePubKey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: 'not-an-array', + content: 'Test', + sig: validSig +} + +export { + mockKind0Event, + mockKind1Event, + mockKind3Event, + mockKind7Event, + mockInvalidEventMissingId, + mockInvalidEventWrongIdLength, + mockInvalidEventMissingPubkey, + mockInvalidEventWrongPubkeyLength, + mockInvalidEventMissingCreatedAt, + mockInvalidEventWrongCreatedAtType, + mockInvalidEventMissingKind, + mockInvalidEventKindOutOfRange, + mockInvalidEventMissingSig, + mockInvalidEventWrongSigLength, + mockInvalidEventTagsNotArray, + alicePubKey, + bobPubKey, + validEventId, + validSig +} diff --git a/test/unit/use-cases/encryption-use-cases-unit.js b/test/unit/use-cases/encryption-use-cases-unit.js new file mode 100644 index 0000000..565e307 --- /dev/null +++ b/test/unit/use-cases/encryption-use-cases-unit.js @@ -0,0 +1,247 @@ +/* + Unit tests for EncryptionUseCases. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import EncryptionUseCases from '../../../src/use-cases/encryption-use-cases.js' + +describe('#encryption-use-cases.js', () => { + let sandbox + let mockAdapters + let mockUseCases + let mockBchjs + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = {} + + // Mock bchjs + mockBchjs = { + Address: { + toCashAddress: sandbox.stub().returns('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') + }, + ECPair: { + fromPublicKey: sandbox.stub().returns({}), + toCashAddress: sandbox.stub().returns('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') + } + } + + // Mock use cases + mockUseCases = { + fulcrum: { + getTransactions: sandbox.stub().resolves({ + transactions: [ + { tx_hash: 'abc123def456' } + ] + }) + }, + rawtransactions: { + getRawTransaction: sandbox.stub().resolves({ + vin: [ + { + scriptSig: { + asm: 'signature 02abc123def456789' + } + } + ] + }) + } + } + + uut = new EncryptionUseCases({ + adapters: mockAdapters, + useCases: mockUseCases, + bchjs: mockBchjs + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new EncryptionUseCases({ useCases: mockUseCases }) + }, /Adapters instance required/) + }) + + it('should require useCases', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new EncryptionUseCases({ adapters: mockAdapters }) + }, /UseCases instance required/) + }) + }) + + describe('#getPublicKey()', () => { + it('should return public key when found', async () => { + const result = await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + + assert.isTrue(result.success) + assert.equal(result.publicKey, '02abc123def456789') + assert.isTrue(mockBchjs.Address.toCashAddress.calledOnce) + assert.isTrue(mockUseCases.fulcrum.getTransactions.calledOnce) + assert.isTrue(mockUseCases.rawtransactions.getRawTransaction.calledOnce) + }) + + it('should return not found when public key does not match', async () => { + // Make the ECPair.toCashAddress return a different address + mockBchjs.ECPair.toCashAddress.returns('bitcoincash:qqq000000000000000000000000000000000000000') + + const result = await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + + assert.isFalse(result.success) + assert.equal(result.publicKey, 'not found') + }) + + it('should throw error when no transaction history', async () => { + mockUseCases.fulcrum.getTransactions.resolves({ + transactions: [] + }) + + try { + await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + assert.fail('Should have thrown an error') + } catch (err) { + assert.equal(err.message, 'No transaction history.') + } + }) + + it('should handle transactions without scriptSig', async () => { + mockUseCases.rawtransactions.getRawTransaction.resolves({ + vin: [ + { txid: 'coinbase' } // No scriptSig + ] + }) + + const result = await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + + assert.isFalse(result.success) + assert.equal(result.publicKey, 'not found') + }) + + it('should handle invalid public key hex gracefully', async () => { + mockUseCases.rawtransactions.getRawTransaction.resolves({ + vin: [ + { + scriptSig: { + asm: 'signature NOT_VALID_HEX' + } + } + ] + }) + + const result = await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + + assert.isFalse(result.success) + assert.equal(result.publicKey, 'not found') + }) + + it('should handle ECPair.fromPublicKey throwing error', async () => { + mockBchjs.ECPair.fromPublicKey.throws(new Error('Invalid public key')) + + const result = await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + + assert.isFalse(result.success) + assert.equal(result.publicKey, 'not found') + }) + + it('should search through multiple transactions', async () => { + // First transaction has no matching public key + mockUseCases.fulcrum.getTransactions.resolves({ + transactions: [ + { tx_hash: 'tx1' }, + { tx_hash: 'tx2' } + ] + }) + + // Return different data for each tx - first tx has input with non-matching key + mockUseCases.rawtransactions.getRawTransaction + .onFirstCall().resolves({ + vin: [ + { + scriptSig: { + asm: 'sig 02aaa111bbb222ccc' + } + } + ] + }) + .onSecondCall().resolves({ + vin: [ + { + scriptSig: { + asm: 'sig 02abc123def456789' + } + } + ] + }) + + // First tx doesn't match, second tx matches + mockBchjs.ECPair.toCashAddress + .onFirstCall().returns('bitcoincash:qqq000000000000000000000000000000000000000') + .onSecondCall().returns('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') + + const result = await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + + assert.isTrue(result.success) + assert.equal(result.publicKey, '02abc123def456789') + assert.equal(mockUseCases.rawtransactions.getRawTransaction.callCount, 2) + }) + + it('should search through multiple inputs in a transaction', async () => { + mockUseCases.rawtransactions.getRawTransaction.resolves({ + vin: [ + { + scriptSig: { + asm: 'sig 02aaa111bbb222ccc' + } + }, + { + scriptSig: { + asm: 'sig 02abc123def456789' + } + } + ] + }) + + // First input doesn't match, second input matches + mockBchjs.ECPair.toCashAddress + .onFirstCall().returns('bitcoincash:qqq000000000000000000000000000000000000000') + .onSecondCall().returns('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') + + const result = await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + + assert.isTrue(result.success) + assert.equal(result.publicKey, '02abc123def456789') + }) + + it('should propagate fulcrum errors', async () => { + const error = new Error('Fulcrum API error') + mockUseCases.fulcrum.getTransactions.rejects(error) + + try { + await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + assert.fail('Should have thrown an error') + } catch (err) { + assert.equal(err.message, 'Fulcrum API error') + } + }) + + it('should propagate rawtransactions errors', async () => { + const error = new Error('RawTransactions API error') + mockUseCases.rawtransactions.getRawTransaction.rejects(error) + + try { + await uut.getPublicKey({ address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' }) + assert.fail('Should have thrown an error') + } catch (err) { + assert.equal(err.message, 'RawTransactions API error') + } + }) + }) +}) diff --git a/test/unit/use-cases/fulcrum-use-cases-unit.js b/test/unit/use-cases/fulcrum-use-cases-unit.js new file mode 100644 index 0000000..395c327 --- /dev/null +++ b/test/unit/use-cases/fulcrum-use-cases-unit.js @@ -0,0 +1,297 @@ +/* + Unit tests for FulcrumUseCases. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' +import BCHJS from '@psf/bch-js' + +import FulcrumUseCases from '../../../src/use-cases/fulcrum-use-cases.js' + +describe('#fulcrum-use-cases.js', () => { + let sandbox + let mockAdapters + let uut + let sortAllTxsStub + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = { + fulcrum: { + get: sandbox.stub().resolves({}), + post: sandbox.stub().resolves({}) + } + } + + // Create a mock BCHJS instance with stubbed sortAllTxs method + const mockBchjs = new BCHJS({ restURL: 'http://localhost:5942/v6/' }) + if (!mockBchjs.Electrumx) { + mockBchjs.Electrumx = {} + } + + // Create a stub that sorts transactions + sortAllTxsStub = sandbox.stub(mockBchjs.Electrumx, 'sortAllTxs') + sortAllTxsStub.callsFake(async (txs, order) => { + const sorted = [...txs].sort((a, b) => { + if (order === 'DESCENDING') { + return (b.height || 0) - (a.height || 0) + } + return (a.height || 0) - (b.height || 0) + }) + return sorted + }) + + // Inject the mocked bchjs instance into the use cases + uut = new FulcrumUseCases({ adapters: mockAdapters, bchjs: mockBchjs }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new FulcrumUseCases() + }, /Adapters instance required/) + }) + + it('should require fulcrum adapter', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new FulcrumUseCases({ adapters: {} }) + }, /Fulcrum adapter required/) + }) + }) + + describe('#getBalance()', () => { + it('should call fulcrum adapter get method', async () => { + const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + mockAdapters.fulcrum.get.resolves({ balance: 1000 }) + + const result = await uut.getBalance({ address }) + + assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/balance/${address}`)) + assert.deepEqual(result, { balance: 1000 }) + }) + }) + + describe('#getBalances()', () => { + it('should call fulcrum adapter post method', async () => { + const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] + mockAdapters.fulcrum.post.resolves({ balances: [] }) + + const result = await uut.getBalances({ addresses }) + + assert.isTrue( + mockAdapters.fulcrum.post.calledOnceWith('electrumx/balance/', { addresses }) + ) + assert.deepEqual(result, { balances: [] }) + }) + }) + + describe('#getUtxos()', () => { + it('should call fulcrum adapter get method', async () => { + const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + mockAdapters.fulcrum.get.resolves({ utxos: [] }) + + const result = await uut.getUtxos({ address }) + + assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/utxos/${address}`)) + assert.deepEqual(result, { utxos: [] }) + }) + }) + + describe('#getUtxosBulk()', () => { + it('should call fulcrum adapter post method', async () => { + const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] + mockAdapters.fulcrum.post.resolves({ utxos: [] }) + + const result = await uut.getUtxosBulk({ addresses }) + + assert.isTrue( + mockAdapters.fulcrum.post.calledOnceWith('electrumx/utxos/', { addresses }) + ) + assert.deepEqual(result, { utxos: [] }) + }) + }) + + describe('#getTransactionDetails()', () => { + it('should call fulcrum adapter get method', async () => { + const txid = 'a'.repeat(64) + mockAdapters.fulcrum.get.resolves({ txid }) + + const result = await uut.getTransactionDetails({ txid }) + + assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/tx/data/${txid}`)) + assert.deepEqual(result, { txid }) + }) + }) + + describe('#getTransactionDetailsBulk()', () => { + it('should call fulcrum adapter post method with verbose', async () => { + const txids = ['a'.repeat(64)] + const verbose = true + mockAdapters.fulcrum.post.resolves({ transactions: [] }) + + const result = await uut.getTransactionDetailsBulk({ txids, verbose }) + + assert.isTrue( + mockAdapters.fulcrum.post.calledOnceWith('electrumx/tx/data', { txids, verbose }) + ) + assert.deepEqual(result, { transactions: [] }) + }) + }) + + describe('#broadcastTransaction()', () => { + it('should call fulcrum adapter post method', async () => { + const txHex = '010203' + mockAdapters.fulcrum.post.resolves({ txid: 'abc' }) + + const result = await uut.broadcastTransaction({ txHex }) + + assert.isTrue( + mockAdapters.fulcrum.post.calledOnceWith('electrumx/tx/broadcast', { txHex }) + ) + assert.deepEqual(result, { txid: 'abc' }) + }) + }) + + describe('#getBlockHeaders()', () => { + it('should call fulcrum adapter get method with height and count', async () => { + const height = 100 + const count = 2 + mockAdapters.fulcrum.get.resolves({ headers: [] }) + + const result = await uut.getBlockHeaders({ height, count }) + + assert.isTrue( + mockAdapters.fulcrum.get.calledOnceWith(`electrumx/block/headers/${height}?count=${count}`) + ) + assert.deepEqual(result, { headers: [] }) + }) + }) + + describe('#getBlockHeadersBulk()', () => { + it('should call fulcrum adapter post method', async () => { + const heights = [{ height: 100, count: 2 }] + mockAdapters.fulcrum.post.resolves({ headers: [] }) + + const result = await uut.getBlockHeadersBulk({ heights }) + + assert.isTrue( + mockAdapters.fulcrum.post.calledOnceWith('electrumx/block/headers', { heights }) + ) + assert.deepEqual(result, { headers: [] }) + }) + }) + + describe('#getTransactions()', () => { + it('should call fulcrum adapter and sort transactions when allTxs is false', async () => { + const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + const allTxs = false + const mockTransactions = [ + { tx_hash: 'aaa', height: 100 }, + { tx_hash: 'bbb', height: 200 }, + { tx_hash: 'ccc', height: 150 } + ] + mockAdapters.fulcrum.get.resolves({ + transactions: mockTransactions + }) + + const result = await uut.getTransactions({ address, allTxs }) + + assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/transactions/${address}`)) + assert.property(result, 'transactions') + // Transactions should be sorted and limited to 100 + if (result.transactions && result.transactions.length > 100) { + assert.isAtMost(result.transactions.length, 100) + } + }) + + it('should return all transactions when allTxs is true', async () => { + const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + const allTxs = true + const mockTransactions = Array(150).fill({ tx_hash: 'aaa', height: 100 }) + mockAdapters.fulcrum.get.resolves({ + transactions: mockTransactions + }) + + const result = await uut.getTransactions({ address, allTxs }) + + assert.property(result, 'transactions') + // All transactions should be returned when allTxs is true + assert.equal(result.transactions.length, 150) + }) + }) + + describe('#getTransactionsBulk()', () => { + it('should call fulcrum adapter and sort transactions for each address', async () => { + const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] + const allTxs = false + const mockResponse = { + transactions: [ + { + transactions: [ + { tx_hash: 'aaa', height: 100 }, + { tx_hash: 'bbb', height: 200 } + ] + } + ] + } + mockAdapters.fulcrum.post.resolves(mockResponse) + + const result = await uut.getTransactionsBulk({ addresses, allTxs }) + + assert.isTrue( + mockAdapters.fulcrum.post.calledOnceWith('electrumx/transactions/', { addresses }) + ) + assert.property(result, 'transactions') + }) + + it('should limit to 100 transactions when allTxs is false', async () => { + const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] + const allTxs = false + const mockTransactions = Array(150).fill({ tx_hash: 'aaa', height: 100 }) + const mockResponse = { + transactions: [ + { + transactions: mockTransactions + } + ] + } + mockAdapters.fulcrum.post.resolves(mockResponse) + + const result = await uut.getTransactionsBulk({ addresses, allTxs }) + + assert.isAtMost(result.transactions[0].transactions.length, 100) + }) + }) + + describe('#getMempool()', () => { + it('should call fulcrum adapter get method', async () => { + const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + mockAdapters.fulcrum.get.resolves({ mempool: [] }) + + const result = await uut.getMempool({ address }) + + assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/unconfirmed/${address}`)) + assert.deepEqual(result, { mempool: [] }) + }) + }) + + describe('#getMempoolBulk()', () => { + it('should call fulcrum adapter post method', async () => { + const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] + mockAdapters.fulcrum.post.resolves({ mempool: [] }) + + const result = await uut.getMempoolBulk({ addresses }) + + assert.isTrue( + mockAdapters.fulcrum.post.calledOnceWith('electrumx/unconfirmed/', { addresses }) + ) + assert.deepEqual(result, { mempool: [] }) + }) + }) +}) diff --git a/test/unit/use-cases/full-node-blockchain-use-cases-unit.js b/test/unit/use-cases/full-node-blockchain-use-cases-unit.js new file mode 100644 index 0000000..3fd3e1a --- /dev/null +++ b/test/unit/use-cases/full-node-blockchain-use-cases-unit.js @@ -0,0 +1,137 @@ +/* + Unit tests for BlockchainUseCases. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import BlockchainUseCases from '../../../src/use-cases/full-node-blockchain-use-cases.js' + +describe('#full-node-blockchain-use-cases.js', () => { + let sandbox + let mockAdapters + let uut + + const createAdapters = () => { + return { + fullNode: { + call: sandbox.stub() + } + } + } + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = createAdapters() + uut = new BlockchainUseCases({ adapters: mockAdapters }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new BlockchainUseCases() + }, /Adapters instance required/) + }) + + it('should require full node adapter', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new BlockchainUseCases({ adapters: {} }) + }, /Full node adapter required/) + }) + }) + + describe('#getBestBlockHash()', () => { + it('should call full node adapter without parameters', async () => { + mockAdapters.fullNode.call.resolves('hash') + + const result = await uut.getBestBlockHash() + + assert.equal(result, 'hash') + assert.isTrue(mockAdapters.fullNode.call.calledOnceWithExactly('getbestblockhash')) + }) + }) + + describe('#getBlockHeaders()', () => { + it('should call adapter for each hash and return aggregated result', async () => { + const hashes = ['a'.repeat(64), 'b'.repeat(64)] + mockAdapters.fullNode.call + .onFirstCall().resolves('header-1') + .onSecondCall().resolves('header-2') + + const result = await uut.getBlockHeaders({ hashes, verbose: true }) + + assert.deepEqual(result, ['header-1', 'header-2']) + assert.isTrue( + mockAdapters.fullNode.call.calledWithExactly( + 'getblockheader', + [hashes[0], true], + `getblockheader-${hashes[0]}` + ) + ) + assert.isTrue( + mockAdapters.fullNode.call.calledWithExactly( + 'getblockheader', + [hashes[1], true], + `getblockheader-${hashes[1]}` + ) + ) + }) + + it('should rethrow errors from adapter', async () => { + const hashes = ['a'.repeat(64)] + mockAdapters.fullNode.call.rejects(new Error('failure')) + + try { + await uut.getBlockHeaders({ hashes }) + assert.fail('Unexpected success') + } catch (err) { + assert.equal(err.message, 'failure') + } + }) + }) + + describe('#getTxOut()', () => { + it('should pass parameters to full node call', async () => { + mockAdapters.fullNode.call.resolves({ value: 1 }) + + const result = await uut.getTxOut({ + txid: 'txid', + n: 0, + includeMempool: true + }) + + assert.deepEqual(result, { value: 1 }) + assert.isTrue( + mockAdapters.fullNode.call.calledOnceWithExactly( + 'gettxout', + ['txid', 0, true] + ) + ) + }) + }) + + describe('#verifyTxOutProofs()', () => { + it('should call adapter for each proof and return aggregated results', async () => { + const proofs = ['proof-1', 'proof-2'] + mockAdapters.fullNode.call.onFirstCall().resolves(['txid-1']) + mockAdapters.fullNode.call.onSecondCall().resolves(['txid-2']) + + const result = await uut.verifyTxOutProofs({ proofs }) + + assert.deepEqual(result, [['txid-1'], ['txid-2']]) + assert.isTrue( + mockAdapters.fullNode.call.calledWithExactly( + 'verifytxoutproof', + ['proof-1'], + `verifytxoutproof-${proofs[0].slice(0, 16)}` + ) + ) + }) + }) +}) diff --git a/test/unit/use-cases/full-node-control-use-cases-unit.js b/test/unit/use-cases/full-node-control-use-cases-unit.js new file mode 100644 index 0000000..caef4b9 --- /dev/null +++ b/test/unit/use-cases/full-node-control-use-cases-unit.js @@ -0,0 +1,53 @@ +/* + Unit tests for ControlUseCases. +*/ + +import { assert } from 'chai' + +import ControlUseCases from '../../../src/use-cases/full-node-control-use-cases.js' + +describe('#full-node-control-use-cases.js', () => { + let mockAdapters + let uut + + beforeEach(() => { + mockAdapters = { + fullNode: { + call: async () => ({}) + } + } + + uut = new ControlUseCases({ adapters: mockAdapters }) + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new ControlUseCases() + }, /Adapters instance required/) + }) + + it('should require full node adapter', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new ControlUseCases({ adapters: {} }) + }, /Full node adapter required/) + }) + }) + + describe('#getNetworkInfo()', () => { + it('should call full node adapter with correct method', async () => { + let capturedMethod = '' + mockAdapters.fullNode.call = async method => { + capturedMethod = method + return { version: 1 } + } + + const result = await uut.getNetworkInfo() + + assert.equal(capturedMethod, 'getnetworkinfo') + assert.deepEqual(result, { version: 1 }) + }) + }) +}) diff --git a/test/unit/use-cases/full-node-dsproof-use-cases-unit.js b/test/unit/use-cases/full-node-dsproof-use-cases-unit.js new file mode 100644 index 0000000..36f4fb3 --- /dev/null +++ b/test/unit/use-cases/full-node-dsproof-use-cases-unit.js @@ -0,0 +1,54 @@ +/* + Unit tests for DSProofUseCases. +*/ + +import { assert } from 'chai' + +import DSProofUseCases from '../../../src/use-cases/full-node-dsproof-use-cases.js' + +describe('#full-node-dsproof-use-cases.js', () => { + let mockAdapters + let uut + + beforeEach(() => { + mockAdapters = { + fullNode: { + call: async () => ({}) + } + } + + uut = new DSProofUseCases({ adapters: mockAdapters }) + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new DSProofUseCases() + }, /Adapters instance required/) + }) + + it('should require full node adapter', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new DSProofUseCases({ adapters: {} }) + }, /Full node adapter required/) + }) + }) + + describe('#getDSProof()', () => { + it('should pass txid and verbose parameters to adapter', async () => { + let capturedArgs = null + mockAdapters.fullNode.call = async (method, params) => { + capturedArgs = { method, params } + return { success: true } + } + + const result = await uut.getDSProof({ txid: 'a'.repeat(64), verbose: 2 }) + + assert.equal(capturedArgs.method, 'getdsproof') + assert.deepEqual(capturedArgs.params, ['a'.repeat(64), 2]) + assert.deepEqual(result, { success: true }) + }) + }) +}) diff --git a/test/unit/use-cases/full-node-mining-use-cases-unit.js b/test/unit/use-cases/full-node-mining-use-cases-unit.js new file mode 100644 index 0000000..815d946 --- /dev/null +++ b/test/unit/use-cases/full-node-mining-use-cases-unit.js @@ -0,0 +1,84 @@ +/* + Unit tests for MiningUseCases. +*/ + +import { assert } from 'chai' + +import MiningUseCases from '../../../src/use-cases/full-node-mining-use-cases.js' + +describe('#full-node-mining-use-cases.js', () => { + let mockAdapters + let uut + + beforeEach(() => { + mockAdapters = { + fullNode: { + call: async () => ({}) + } + } + + uut = new MiningUseCases({ adapters: mockAdapters }) + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new MiningUseCases() + }, /Adapters instance required/) + }) + + it('should require full node adapter', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new MiningUseCases({ adapters: {} }) + }, /Full node adapter required/) + }) + }) + + describe('#getMiningInfo()', () => { + it('should call full node adapter with correct method', async () => { + let capturedMethod = '' + mockAdapters.fullNode.call = async method => { + capturedMethod = method + return { blocks: 100, difficulty: 1.5 } + } + + const result = await uut.getMiningInfo() + + assert.equal(capturedMethod, 'getmininginfo') + assert.deepEqual(result, { blocks: 100, difficulty: 1.5 }) + }) + }) + + describe('#getNetworkHashPS()', () => { + it('should call full node adapter with correct method and default params', async () => { + let capturedMethod = '' + let capturedParams = [] + mockAdapters.fullNode.call = async (method, params) => { + capturedMethod = method + capturedParams = params + return 1234567890 + } + + const result = await uut.getNetworkHashPS({ nblocks: 120, height: -1 }) + + assert.equal(capturedMethod, 'getnetworkhashps') + assert.deepEqual(capturedParams, [120, -1]) + assert.equal(result, 1234567890) + }) + + it('should call full node adapter with custom params', async () => { + let capturedParams = [] + mockAdapters.fullNode.call = async (method, params) => { + capturedParams = params + return 9876543210 + } + + const result = await uut.getNetworkHashPS({ nblocks: 240, height: 1000 }) + + assert.deepEqual(capturedParams, [240, 1000]) + assert.equal(result, 9876543210) + }) + }) +}) diff --git a/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js b/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js new file mode 100644 index 0000000..95dae8d --- /dev/null +++ b/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js @@ -0,0 +1,267 @@ +/* + Unit tests for RawTransactionsUseCases. +*/ + +import { assert } from 'chai' + +import RawTransactionsUseCases from '../../../src/use-cases/full-node-rawtransactions-use-cases.js' + +describe('#full-node-rawtransactions-use-cases.js', () => { + let mockAdapters + let uut + + beforeEach(() => { + mockAdapters = { + fullNode: { + call: async () => ({}) + } + } + + uut = new RawTransactionsUseCases({ adapters: mockAdapters }) + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new RawTransactionsUseCases() + }, /Adapters instance required/) + }) + + it('should require full node adapter', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new RawTransactionsUseCases({ adapters: {} }) + }, /Full node adapter required/) + }) + }) + + describe('#decodeRawTransaction()', () => { + it('should call full node adapter with correct method', async () => { + let capturedMethod = '' + let capturedParams = [] + mockAdapters.fullNode.call = async (method, params) => { + capturedMethod = method + capturedParams = params + return { txid: 'abc123', version: 2 } + } + + const result = await uut.decodeRawTransaction({ hex: '01000000' }) + + assert.equal(capturedMethod, 'decoderawtransaction') + assert.deepEqual(capturedParams, ['01000000']) + assert.deepEqual(result, { txid: 'abc123', version: 2 }) + }) + }) + + describe('#decodeRawTransactions()', () => { + it('should call full node adapter for each hex in parallel', async () => { + const callCount = { count: 0 } + mockAdapters.fullNode.call = async (method, params) => { + callCount.count++ + return { txid: `tx${callCount.count}`, hex: params[0] } + } + + const hexes = ['hex1', 'hex2', 'hex3'] + const result = await uut.decodeRawTransactions({ hexes }) + + assert.equal(callCount.count, 3) + assert.equal(result.length, 3) + assert.equal(result[0].txid, 'tx1') + assert.equal(result[1].txid, 'tx2') + assert.equal(result[2].txid, 'tx3') + }) + }) + + describe('#decodeScript()', () => { + it('should call full node adapter with correct method', async () => { + let capturedMethod = '' + let capturedParams = [] + mockAdapters.fullNode.call = async (method, params) => { + capturedMethod = method + capturedParams = params + return { asm: 'OP_DUP OP_HASH160', type: 'pubkeyhash' } + } + + const result = await uut.decodeScript({ hex: '76a914' }) + + assert.equal(capturedMethod, 'decodescript') + assert.deepEqual(capturedParams, ['76a914']) + assert.deepEqual(result, { asm: 'OP_DUP OP_HASH160', type: 'pubkeyhash' }) + }) + }) + + describe('#decodeScripts()', () => { + it('should call full node adapter for each hex in parallel', async () => { + const callCount = { count: 0 } + mockAdapters.fullNode.call = async (method, params) => { + callCount.count++ + return { asm: `script${callCount.count}`, hex: params[0] } + } + + const hexes = ['script1', 'script2'] + const result = await uut.decodeScripts({ hexes }) + + assert.equal(callCount.count, 2) + assert.equal(result.length, 2) + }) + }) + + describe('#getRawTransaction()', () => { + it('should call full node adapter with verbose=false by default', async () => { + let capturedParams = [] + mockAdapters.fullNode.call = async (method, params) => { + capturedParams = params + return '01000000' + } + + await uut.getRawTransaction({ txid: 'abc123' }) + + assert.deepEqual(capturedParams, ['abc123', 0]) + }) + + it('should call full node adapter with verbose=true when specified', async () => { + let capturedParams = [] + mockAdapters.fullNode.call = async (method, params) => { + capturedParams = params + return { txid: 'abc123', version: 2 } + } + + await uut.getRawTransaction({ txid: 'abc123', verbose: true }) + + assert.deepEqual(capturedParams, ['abc123', 1]) + }) + }) + + describe('#getRawTransactions()', () => { + it('should call full node adapter for each txid in parallel', async () => { + const callCount = { count: 0 } + mockAdapters.fullNode.call = async (method, params) => { + callCount.count++ + return { txid: params[0], version: 2 } + } + + const txids = ['tx1', 'tx2'] + const result = await uut.getRawTransactions({ txids, verbose: true }) + + assert.equal(callCount.count, 2) + assert.equal(result.length, 2) + }) + }) + + describe('#getRawTransactionWithHeight()', () => { + it('should return transaction without height when verbose=false', async () => { + mockAdapters.fullNode.call = async (method, params) => { + if (method === 'getrawtransaction') { + return '01000000' + } + return {} + } + + const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: false }) + + assert.equal(result, '01000000') + }) + + it('should fetch and append height when verbose=true and blockhash exists', async () => { + let callCount = 0 + mockAdapters.fullNode.call = async (method, params) => { + callCount++ + if (method === 'getrawtransaction') { + return { txid: 'abc123', blockhash: 'block123' } + } + if (method === 'getblockheader') { + return { height: 100, hash: 'block123' } + } + return {} + } + + const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: true }) + + assert.equal(callCount, 2) + assert.equal(result.height, 100) + assert.equal(result.txid, 'abc123') + }) + + it('should handle block header lookup failure gracefully', async () => { + let callCount = 0 + mockAdapters.fullNode.call = async (method, params) => { + callCount++ + if (method === 'getrawtransaction') { + return { txid: 'abc123', blockhash: 'block123' } + } + if (method === 'getblockheader') { + throw new Error('Block not found') + } + return {} + } + + const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: true }) + + assert.equal(callCount, 2) + assert.isNull(result.height) + assert.equal(result.txid, 'abc123') + }) + }) + + describe('#getBlockHeader()', () => { + it('should call full node adapter with correct method', async () => { + let capturedMethod = '' + let capturedParams = [] + mockAdapters.fullNode.call = async (method, params) => { + capturedMethod = method + capturedParams = params + return { height: 100, hash: 'block123' } + } + + const result = await uut.getBlockHeader({ blockHash: 'block123', verbose: true }) + + assert.equal(capturedMethod, 'getblockheader') + assert.deepEqual(capturedParams, ['block123', true]) + assert.deepEqual(result, { height: 100, hash: 'block123' }) + }) + }) + + describe('#sendRawTransaction()', () => { + it('should call full node adapter with correct method', async () => { + let capturedMethod = '' + let capturedParams = [] + mockAdapters.fullNode.call = async (method, params) => { + capturedMethod = method + capturedParams = params + return 'txid123' + } + + const result = await uut.sendRawTransaction({ hex: '01000000' }) + + assert.equal(capturedMethod, 'sendrawtransaction') + assert.deepEqual(capturedParams, ['01000000']) + assert.equal(result, 'txid123') + }) + }) + + describe('#sendRawTransactions()', () => { + it('should send transactions serially, not in parallel', async () => { + const callOrder = [] + mockAdapters.fullNode.call = async (method, params) => { + callOrder.push(params[0]) + // Simulate some async work + await new Promise(resolve => setTimeout(resolve, 10)) + return `txid-${params[0]}` + } + + const hexes = ['hex1', 'hex2', 'hex3'] + const startTime = Date.now() + const result = await uut.sendRawTransactions({ hexes }) + const endTime = Date.now() + + // Should take at least 30ms if serial (3 * 10ms) + assert.isAtLeast(endTime - startTime, 25) + assert.deepEqual(callOrder, ['hex1', 'hex2', 'hex3']) + assert.equal(result.length, 3) + assert.equal(result[0], 'txid-hex1') + assert.equal(result[1], 'txid-hex2') + assert.equal(result[2], 'txid-hex3') + }) + }) +}) diff --git a/test/unit/use-cases/price-use-cases-unit.js b/test/unit/use-cases/price-use-cases-unit.js new file mode 100644 index 0000000..cf0f30a --- /dev/null +++ b/test/unit/use-cases/price-use-cases-unit.js @@ -0,0 +1,103 @@ +/* + Unit tests for PriceUseCases. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' + +import PriceUseCases from '../../../src/use-cases/price-use-cases.js' + +describe('#price-use-cases.js', () => { + let sandbox + let mockAdapters + let mockAxios + let mockConfig + let uut + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockAdapters = {} + + mockConfig = { + restURL: 'http://localhost:3000/v5/' + } + + // Mock axios + mockAxios = { + request: sandbox.stub() + } + + uut = new PriceUseCases({ + adapters: mockAdapters, + axios: mockAxios, + config: mockConfig + }) + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new PriceUseCases() + }, /Adapters instance required/) + }) + }) + + describe('#getBCHUSD()', () => { + it('should return BCH price from Coinex API', async () => { + const mockPrice = 250.5 + mockAxios.request.resolves({ + data: { + data: { + ticker: { + last: mockPrice.toString() + } + } + } + }) + + const result = await uut.getBCHUSD() + + assert.equal(result, mockPrice) + assert.isTrue(mockAxios.request.calledOnce) + const callArgs = mockAxios.request.getCall(0).args[0] + assert.equal(callArgs.method, 'get') + assert.equal(callArgs.baseURL, 'https://api.coinex.com/v1/market/ticker?market=bchusdt') + assert.equal(callArgs.timeout, 15000) + }) + + it('should handle errors', async () => { + const error = new Error('API error') + mockAxios.request.rejects(error) + + try { + await uut.getBCHUSD() + assert.fail('Should have thrown an error') + } catch (err) { + assert.equal(err.message, 'API error') + } + }) + }) + + describe('#getPsffppWritePrice()', () => { + it('should handle errors properly', async () => { + // Note: Full unit testing of getPsffppWritePrice is difficult due to dynamic imports + // of SlpWallet and PSFFPP. Integration tests should verify the full flow. + // This test verifies that errors are properly handled and propagated. + try { + // This will likely fail in unit test environment without proper setup + // but we verify error handling works correctly + await uut.getPsffppWritePrice() + // If it succeeds, that's also acceptable + } catch (err) { + // Verify error is properly formatted + assert.isTrue(err instanceof Error) + // Verify error was logged (indirectly through wlogger) + } + }) + }) +}) diff --git a/test/unit/use-cases/slp-use-cases-unit.js b/test/unit/use-cases/slp-use-cases-unit.js new file mode 100644 index 0000000..0a87910 --- /dev/null +++ b/test/unit/use-cases/slp-use-cases-unit.js @@ -0,0 +1,296 @@ +/* + Unit tests for SlpUseCases. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' +import BCHJS from '@psf/bch-js' + +import SlpUseCases from '../../../src/use-cases/slp-use-cases.js' + +describe('#slp-use-cases.js', () => { + let sandbox + let mockAdapters + let mockConfig + let uut + let mockBchjs + let mockWallet + let mockSlpTokenMedia + + beforeEach(() => { + sandbox = sinon.createSandbox() + mockConfig = { + restURL: 'http://localhost:3000/v5/', + ipfsGateway: 'p2wdb-gateway-678.fullstack.cash' + } + + mockAdapters = { + slpIndexer: { + get: sandbox.stub().resolves({}), + post: sandbox.stub().resolves({}) + } + } + + // Create mock BCHJS + mockBchjs = new BCHJS({ restURL: 'http://localhost:5942/v6/' }) + mockBchjs.Electrumx = { + txData: sandbox.stub().resolves({ + details: { + vout: [ + { + scriptPubKey: { + hex: '6a0c48656c6c6f20576f726c6421' + } + } + ] + } + }) + } + mockBchjs.Script = { + toASM: sandbox.stub().returns('OP_RETURN 48656c6c6f20576f726c6421') + } + + // Create mock wallet + mockWallet = { + walletInfoPromise: Promise.resolve(), + getTransactions: sandbox.stub().resolves([]), + getTxData: sandbox.stub().resolves([{ + vin: [{ + address: 'bitcoincash:test123' + }] + }]) + } + + // Create mock SlpTokenMedia + mockSlpTokenMedia = { + getIcon: sandbox.stub().resolves({ tokenIcon: 'test-icon.png' }) + } + + // Mock the imports + uut = new SlpUseCases({ + adapters: mockAdapters, + bchjs: mockBchjs, + config: mockConfig + }) + + // Replace the wallet initialization with our mocks + uut.wallet = mockWallet + uut.slpTokenMedia = mockSlpTokenMedia + uut.walletInitialized = true + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('#constructor()', () => { + it('should require adapters', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new SlpUseCases() + }, /Adapters instance required/) + }) + + it('should require slpIndexer adapter', () => { + assert.throws(() => { + // eslint-disable-next-line no-new + new SlpUseCases({ adapters: {} }) + }, /SLP Indexer adapter required/) + }) + }) + + describe('#getStatus()', () => { + it('should call slpIndexer adapter get method', async () => { + mockAdapters.slpIndexer.get.resolves({ status: 'ok' }) + + const result = await uut.getStatus() + + assert.isTrue(mockAdapters.slpIndexer.get.calledOnceWith('slp/status/')) + assert.deepEqual(result, { status: 'ok' }) + }) + }) + + describe('#getAddress()', () => { + it('should call slpIndexer adapter post method', async () => { + const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + mockAdapters.slpIndexer.post.resolves({ balance: 1000 }) + + const result = await uut.getAddress({ address }) + + assert.isTrue( + mockAdapters.slpIndexer.post.calledOnceWith('slp/address/', { address }) + ) + assert.deepEqual(result, { balance: 1000 }) + }) + }) + + describe('#getTxid()', () => { + it('should call slpIndexer adapter post method', async () => { + const txid = 'a'.repeat(64) + mockAdapters.slpIndexer.post.resolves({ txid }) + + const result = await uut.getTxid({ txid }) + + assert.isTrue( + mockAdapters.slpIndexer.post.calledOnceWith('slp/tx/', { txid }) + ) + assert.deepEqual(result, { txid }) + }) + }) + + describe('#getTokenStats()', () => { + it('should call slpIndexer adapter post method', async () => { + const tokenId = 'a'.repeat(64) + const withTxHistory = false + mockAdapters.slpIndexer.post.resolves({ tokenData: {} }) + + const result = await uut.getTokenStats({ tokenId, withTxHistory }) + + assert.isTrue( + mockAdapters.slpIndexer.post.calledOnceWith('slp/token/', { tokenId, withTxHistory }) + ) + assert.deepEqual(result, { tokenData: {} }) + }) + }) + + describe('#getTokenData()', () => { + it('should get token data with mutable and immutable data', async () => { + const tokenId = 'a'.repeat(64) + const tokenStats = { + tokenData: { + documentUri: 'ipfs://test123', + documentHash: 'b'.repeat(64) + } + } + mockAdapters.slpIndexer.post.resolves(tokenStats) + + // Mock decodeOpReturn to return JSON with mda + sandbox.stub(uut, 'decodeOpReturn').resolves(JSON.stringify({ mda: 'bitcoincash:test123' })) + sandbox.stub(uut, 'getMutableCid').resolves('mutable-cid-123') + + const result = await uut.getTokenData({ tokenId }) + + assert.property(result, 'genesisData') + assert.property(result, 'immutableData') + assert.property(result, 'mutableData') + }) + + it('should handle errors when getting mutable data', async () => { + const tokenId = 'a'.repeat(64) + const tokenStats = { + tokenData: { + documentUri: 'ipfs://test123', + documentHash: 'b'.repeat(64) + } + } + mockAdapters.slpIndexer.post.resolves(tokenStats) + + sandbox.stub(uut, 'getMutableCid').rejects(new Error('Test error')) + + const result = await uut.getTokenData({ tokenId }) + + assert.property(result, 'genesisData') + assert.property(result, 'immutableData') + assert.equal(result.mutableData, '') + }) + }) + + describe('#decodeOpReturn()', () => { + it('should decode OP_RETURN data from transaction', async () => { + const txid = 'a'.repeat(64) + const mockTxData = { + details: { + vout: [ + { + scriptPubKey: { + hex: '6a0c48656c6c6f20576f726c6421' + } + } + ] + } + } + mockBchjs.Electrumx.txData.resolves(mockTxData) + mockBchjs.Script.toASM.returns('OP_RETURN 48656c6c6f20576f726c6421') + + const result = await uut.decodeOpReturn({ txid }) + + assert.isTrue(mockBchjs.Electrumx.txData.calledOnceWith(txid)) + assert.isString(result) + }) + + it('should throw error if txid is not a string', async () => { + try { + await uut.decodeOpReturn({ txid: null }) + assert.fail('Should have thrown an error') + } catch (err) { + assert.include(err.message, 'txid must be a string') + } + }) + }) + + describe('#getCIDData()', () => { + it('should fetch IPFS data from CID', async () => { + const cid = 'ipfs://test123' + const mockData = { name: 'Test Token' } + + // Mock axios + const axios = await import('axios') + sandbox.stub(axios.default, 'get').resolves({ data: mockData }) + + const result = await uut.getCIDData({ cid }) + + assert.deepEqual(result, mockData) + }) + + it('should throw error if cid is not a string', async () => { + try { + await uut.getCIDData({ cid: null }) + assert.fail('Should have thrown an error') + } catch (err) { + assert.include(err.message, 'cid must be a string') + } + }) + }) + + describe('#getMutableCid()', () => { + it('should extract mutable CID from token stats', async () => { + const tokenStats = { + documentHash: 'a'.repeat(64) + } + + const mockOpReturn = JSON.stringify({ mda: 'bitcoincash:test123' }) + sandbox.stub(uut, 'decodeOpReturn').resolves(mockOpReturn) + + mockWallet.getTransactions.resolves([ + { + tx_hash: 'b'.repeat(64), + height: 100 + } + ]) + + mockWallet.getTxData.resolves([{ + vin: [{ + address: 'bitcoincash:test123' + }] + }]) + + // Mock decodeOpReturn for the transaction + uut.decodeOpReturn.onSecondCall().resolves(JSON.stringify({ cid: 'ipfs://mutable-cid-123', ts: 1234567890 })) + + const result = await uut.getMutableCid({ tokenStats }) + + assert.isString(result) + }) + + it('should return false if no documentHash in tokenStats', async () => { + const tokenStats = {} + + try { + await uut.getMutableCid({ tokenStats }) + assert.fail('Should have thrown an error') + } catch (err) { + assert.include(err.message, 'No documentHash property found') + } + }) + }) +})