From 9cf897db0d6a1530889e2c5f4c126b329ba096a2 Mon Sep 17 00:00:00 2001 From: Gazzy-Lee Date: Tue, 24 Mar 2026 17:03:40 +0100 Subject: [PATCH] feat: add Redis leaky bucket rate limiter with Sybil analysis flagging --- index.js | 28 + middleware/rateLimiter.js | 121 ++ package-lock.json | 1433 ++++++++---------------- package.json | 1 + rateLimiter.test.js | 443 ++++++++ src/config/redis.js | 65 ++ src/services/leakyBucketRateLimiter.js | 187 ++++ src/services/sybilAnalysisService.js | 129 +++ 8 files changed, 1419 insertions(+), 988 deletions(-) create mode 100644 middleware/rateLimiter.js create mode 100644 rateLimiter.test.js create mode 100644 src/config/redis.js create mode 100644 src/services/leakyBucketRateLimiter.js create mode 100644 src/services/sybilAnalysisService.js diff --git a/index.js b/index.js index f291d67..6255558 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,8 @@ const { SorobanSubscriptionVerifier } = require('./src/services/sorobanSubscript const { buildAuditLogCsv } = require('./src/utils/export/auditLogCsv'); const { buildAuditLogPdf } = require('./src/utils/export/auditLogPdf'); const { getRequestIp } = require('./src/utils/requestIp'); +const { getRedisClient, closeRedisClient } = require('./src/config/redis'); +const { createRateLimiter } = require('./middleware/rateLimiter'); /** * Create the Express application with injectable services for testing. @@ -38,6 +40,19 @@ function createApp(dependencies = {}) { app.use(cors()); app.use(express.json()); + // Leaky-bucket rate limiting per wallet address (requires Redis). + if (dependencies.rateLimiter) { + app.use('/api', dependencies.rateLimiter); + } else if (process.env.REDIS_URL || process.env.REDIS_HOST) { + app.use('/api', createRateLimiter({ + redis: getRedisClient(), + bucketCapacity: Number(process.env.RATE_LIMIT_CAPACITY || 60), + leakRatePerSecond: Number(process.env.RATE_LIMIT_LEAK_RATE || 1), + blockDurationSeconds: Number(process.env.RATE_LIMIT_BLOCK_SECONDS || 300), + sybilThreshold: Number(process.env.SYBIL_THRESHOLD || 3), + })); + } + app.get('/', (req, res) => { res.json({ project: 'SubStream Protocol', @@ -337,6 +352,19 @@ app.use(cors()); app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true })); +// Leaky-bucket rate limiting per wallet address +if (process.env.REDIS_URL || process.env.REDIS_HOST) { + const { createRateLimiter: createRL } = require('./middleware/rateLimiter'); + const { getRedisClient: getRC } = require('./src/config/redis'); + app.use(createRL({ + redis: getRC(), + bucketCapacity: Number(process.env.RATE_LIMIT_CAPACITY || 60), + leakRatePerSecond: Number(process.env.RATE_LIMIT_LEAK_RATE || 1), + blockDurationSeconds: Number(process.env.RATE_LIMIT_BLOCK_SECONDS || 300), + sybilThreshold: Number(process.env.SYBIL_THRESHOLD || 3), + })); +} + // Routes app.use('/auth', require('./routes/auth')); app.use('/content', require('./routes/content')); diff --git a/middleware/rateLimiter.js b/middleware/rateLimiter.js new file mode 100644 index 0000000..5e692de --- /dev/null +++ b/middleware/rateLimiter.js @@ -0,0 +1,121 @@ +/** + * Express middleware – per-wallet Leaky Bucket rate limiting with Sybil flagging. + * + * Usage: + * const { createRateLimiter } = require('./middleware/rateLimiter'); + * app.use('/api', createRateLimiter({ redis, bucketCapacity: 60 })); + * + * The middleware extracts the wallet address from (in priority order): + * 1. req.user.address (set by auth middleware) + * 2. req.user.publicKey (Stellar auth) + * 3. req.body.walletAddress + * 4. req.query.walletAddress || req.query.publicKey + * + * When a wallet cannot be determined the request falls through to the + * next middleware so unauthenticated routes still work. + */ + +const { + LeakyBucketRateLimiter, +} = require("../src/services/leakyBucketRateLimiter"); +const { + SybilAnalysisService, +} = require("../src/services/sybilAnalysisService"); +const { getRequestIp } = require("../src/utils/requestIp"); + +/** + * Extract the wallet / public-key identifier from the request. + * + * @param {import('express').Request} req + * @returns {string|null} + */ +function extractWallet(req) { + if (req.user?.address) return req.user.address; + if (req.user?.publicKey) return req.user.publicKey; + if (req.body?.walletAddress) return req.body.walletAddress; + if (req.query?.walletAddress) return req.query.walletAddress; + if (req.query?.publicKey) return req.query.publicKey; + return null; +} + +/** + * Create the rate-limiting middleware. + * + * @param {object} options + * @param {import('ioredis').Redis} options.redis Redis client instance. + * @param {number} [options.bucketCapacity=60] Max burst size. + * @param {number} [options.leakRatePerSecond=1] Tokens drained per second. + * @param {number} [options.blockDurationSeconds=300] Temp-block length after overflow. + * @param {number} [options.sybilThreshold=3] Violations before Sybil flag. + * @param {boolean} [options.skipIfNoWallet=true] Pass through when wallet is unknown. + * @returns {import('express').RequestHandler} + */ +function createRateLimiter(options = {}) { + const { redis, skipIfNoWallet = true } = options; + + if (!redis) { + throw new Error("createRateLimiter requires a redis client instance"); + } + + const limiter = new LeakyBucketRateLimiter(redis, { + bucketCapacity: options.bucketCapacity, + leakRatePerSecond: options.leakRatePerSecond, + blockDurationSeconds: options.blockDurationSeconds, + sybilThreshold: options.sybilThreshold, + }); + + const sybil = new SybilAnalysisService(redis, { + flagThreshold: options.sybilThreshold, + }); + + return async function rateLimiterMiddleware(req, res, next) { + const wallet = extractWallet(req); + + if (!wallet) { + if (skipIfNoWallet) return next(); + return res.status(400).json({ + success: false, + error: "Wallet address required for rate-limited endpoints", + }); + } + + try { + const result = await limiter.consume(wallet); + + // Always attach rate-limit headers so clients can self-throttle. + res.set("X-RateLimit-Limit", String(result.capacity)); + res.set( + "X-RateLimit-Remaining", + String(Math.max(0, Math.floor(result.capacity - result.currentLevel))), + ); + + if (result.allowed) { + return next(); + } + + // --- Request denied --- + + // Flag for Sybil analysis when violations cross the threshold. + if (result.violations !== undefined) { + await sybil.evaluate(wallet, result.violations, { + endpoint: req.originalUrl, + ip: getRequestIp(req), + }); + } + + res.set("Retry-After", String(result.retryAfterSeconds)); + + return res.status(429).json({ + success: false, + error: "Rate limit exceeded. You have been temporarily blocked.", + retryAfterSeconds: result.retryAfterSeconds, + }); + } catch (err) { + // If Redis is unavailable, fail open so the API stays usable. + console.error("[RateLimiter] Redis error – failing open:", err.message); + return next(); + } + }; +} + +module.exports = { createRateLimiter, extractWallet }; diff --git a/package-lock.json b/package-lock.json index 689cba3..7422354 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "ethers": "^6.8.1", "express": "^5.2.1", "form-data": "^4.0.0", + "ioredis": "^5.6.1", "ipfs-http-client": "^60.0.1", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", @@ -570,6 +571,12 @@ "node": ">=14" } }, + "node_modules/@ioredis/commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", + "license": "MIT" + }, "node_modules/@ipld/car": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@ipld/car/-/car-3.2.4.tgz", @@ -1085,25 +1092,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", - "node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "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", "dependencies": { "@noble/hashes": "2.0.1" @@ -1201,58 +1189,6 @@ "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", "license": "Apache-2.0 OR MIT", - "node_modules/@stellar/js-xdr": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.2.tgz", - "integrity": "sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==", - "license": "Apache-2.0" - }, - "node_modules/@stellar/stellar-base": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-14.1.0.tgz", - "integrity": "sha512-A8kFli6QGy22SRF45IjgPAJfUNGjnI+R7g4DF5NZYVsD1kGf7B4ITyc4OPclLV9tqNI4/lXxafGEw0JEUbHixw==", - "license": "Apache-2.0", - "dependencies": { - "@noble/curves": "^1.9.6", - "@stellar/js-xdr": "^3.1.2", - "base32.js": "^0.1.0", - "bignumber.js": "^9.3.1", - "buffer": "^6.0.3", - "sha.js": "^2.4.12" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@stellar/stellar-sdk": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-14.6.1.tgz", - "integrity": "sha512-A1rQWDLdUasXkMXnYSuhgep+3ZZzyuXJKdt5/KAIc0gkmSp906HTvUpbT4pu+bVr41tu0+J4Ugz9J4BQAGGytg==", - "license": "Apache-2.0", - "dependencies": { - "@stellar/stellar-base": "^14.1.0", - "axios": "^1.13.3", - "bignumber.js": "^9.3.1", - "commander": "^14.0.2", - "eventsource": "^2.0.2", - "feaxios": "^0.0.23", - "randombytes": "^2.1.0", - "toml": "^3.0.0", - "urijs": "^1.19.1" - }, - "bin": { - "stellar-js": "bin/stellar-js" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", "dependencies": { "multiformats": "^13.0.0" } @@ -1511,44 +1447,6 @@ "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", "license": "Apache-2.0 OR MIT", - "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.13.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", - "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", "dependencies": { "multiformats": "^13.0.0" } @@ -1627,74 +1525,15 @@ "license": "(Apache-2.0 AND MIT)" }, "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", "license": "MIT", "dependencies": { - "@noble/hashes": "1.3.2" - "node_modules/base32.js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", - "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "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/baseline-browser-mapping": { - "version": "2.10.10", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", - "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "@noble/hashes": "1.8.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "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/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", "engines": { - "node": ">= 16" + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -1793,31 +1632,6 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "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": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", @@ -1869,23 +1683,20 @@ "license": "Apache-2.0" }, "node_modules/@stellar/stellar-base": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-13.1.0.tgz", - "integrity": "sha512-90EArG+eCCEzDGj3OJNoCtwpWDwxjv+rs/RNPhvg4bulpjN/CSRj+Ys/SalRcfM4/WRC5/qAfjzmJBAuquWhkA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-14.1.0.tgz", + "integrity": "sha512-A8kFli6QGy22SRF45IjgPAJfUNGjnI+R7g4DF5NZYVsD1kGf7B4ITyc4OPclLV9tqNI4/lXxafGEw0JEUbHixw==", "license": "Apache-2.0", "dependencies": { + "@noble/curves": "^1.9.6", "@stellar/js-xdr": "^3.1.2", "base32.js": "^0.1.0", - "bignumber.js": "^9.1.2", + "bignumber.js": "^9.3.1", "buffer": "^6.0.3", - "sha.js": "^2.3.6", - "tweetnacl": "^1.0.3" + "sha.js": "^2.4.12" }, "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "sodium-native": "^4.3.3" + "node": ">=20.0.0" } }, "node_modules/@stellar/stellar-sdk": { @@ -1893,29 +1704,6 @@ "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-14.6.1.tgz", "integrity": "sha512-A1rQWDLdUasXkMXnYSuhgep+3ZZzyuXJKdt5/KAIc0gkmSp906HTvUpbT4pu+bVr41tu0+J4Ugz9J4BQAGGytg==", "license": "Apache-2.0", - "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": { "@stellar/stellar-base": "^14.1.0", "axios": "^1.13.3", @@ -1934,43 +1722,11 @@ "node": ">=20.0.0" } }, - "node_modules/@stellar/stellar-sdk/node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@stellar/stellar-sdk/node_modules/@stellar/stellar-base": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-14.1.0.tgz", - "integrity": "sha512-A8kFli6QGy22SRF45IjgPAJfUNGjnI+R7g4DF5NZYVsD1kGf7B4ITyc4OPclLV9tqNI4/lXxafGEw0JEUbHixw==", - "license": "Apache-2.0", - "dependencies": { - "@noble/curves": "^1.9.6", - "@stellar/js-xdr": "^3.1.2", - "base32.js": "^0.1.0", - "bignumber.js": "^9.3.1", - "buffer": "^6.0.3", - "sha.js": "^2.4.12" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", @@ -2067,12 +1823,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~6.19.2" } }, "node_modules/@types/normalize-package-data": { @@ -2099,10 +1855,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, - "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": { "@types/yargs-parser": "*" @@ -2219,8 +1971,6 @@ }, "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", @@ -2240,19 +1990,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2661,8 +2398,6 @@ }, "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", @@ -2805,8 +2540,6 @@ }, "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" @@ -2832,8 +2565,6 @@ }, "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", @@ -2845,8 +2576,6 @@ }, "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", @@ -2896,9 +2625,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001780", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", - "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", "dev": true, "funding": [ { @@ -3061,6 +2790,15 @@ "node": ">=12" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3189,8 +2927,6 @@ }, "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" @@ -3202,8 +2938,6 @@ }, "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" @@ -3218,8 +2952,6 @@ }, "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" @@ -3227,8 +2959,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" @@ -3249,8 +2979,6 @@ }, "node_modules/cors": { "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -3322,8 +3050,6 @@ }, "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" @@ -3422,10 +3148,17 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "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" @@ -3476,8 +3209,6 @@ }, "node_modules/dotenv": { "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -3488,8 +3219,6 @@ }, "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", @@ -3511,8 +3240,6 @@ }, "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-fetch": { @@ -3528,9 +3255,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.321", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", - "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "version": "1.5.322", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.322.tgz", + "integrity": "sha512-vFU34OcrvMcH66T+dYC3G4nURmgfDVewMIu6Q2urXpumAPSMmzvcn04KVVV8Opikq8Vs5nUbO/8laNhNRqSzYw==", "dev": true, "license": "ISC" }, @@ -3556,8 +3283,6 @@ }, "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" @@ -3601,8 +3326,6 @@ }, "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" @@ -3610,8 +3333,6 @@ }, "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" @@ -3619,8 +3340,6 @@ }, "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" @@ -3656,8 +3375,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": { @@ -3686,8 +3403,6 @@ }, "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" @@ -3721,6 +3436,18 @@ "node": ">=14.0.0" } }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/ethers/node_modules/@noble/hashes": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", @@ -3733,21 +3460,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/ethers/node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/ethers/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT" - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -3833,8 +3545,6 @@ }, "node_modules/express": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -3996,8 +3706,6 @@ }, "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", @@ -4120,29 +3828,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "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/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.6" @@ -4150,8 +3835,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" @@ -4181,8 +3864,6 @@ }, "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" @@ -4219,8 +3900,6 @@ }, "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", @@ -4259,8 +3938,6 @@ }, "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", @@ -4320,8 +3997,6 @@ }, "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" @@ -4360,10 +4035,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", - "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": { "multiformats": "^9.4.2" @@ -4402,8 +4073,6 @@ }, "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" @@ -4416,20 +4085,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "node_modules/eventsource": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", - "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -4449,8 +4104,6 @@ }, "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" @@ -4498,8 +4151,6 @@ }, "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", @@ -4522,36 +4173,12 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/feaxios": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/feaxios/-/feaxios-0.0.23.tgz", - "integrity": "sha512-eghR0A21fvbkcQBgZuMfQhrXxJzC0GNUGC9fXhBge33D+mFDTwl0aJ35zoQQn575BhyjQitRc5N4f+L4cP708g==", - "license": "MIT", - "dependencies": { - "is-retry-allowed": "^3.0.0" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, "engines": { "node": ">=10.17.0" } }, "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" @@ -4622,45 +4249,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "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/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", "engines": { "node": ">=0.8.19" @@ -4670,10 +4258,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "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": ">=8" @@ -4686,11 +4270,6 @@ "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.", "dev": true, "license": "ISC", - "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": { "once": "^1.3.0", "wrappy": "1" @@ -4698,8 +4277,6 @@ }, "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/interface-blockstore": { @@ -4755,6 +4332,30 @@ "integrity": "sha512-+WvfEZnFUhRwFxgz+QCQi7UC6o9AM0EHM9bpIe2Nhqb100NHCsTvNAn4eJgvgV2/tmLo1MP9nGxQKEcZTAueLA==", "license": "Apache-2.0 OR MIT" }, + "node_modules/ioredis": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz", + "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.5.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, "node_modules/ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -4766,275 +4367,38 @@ }, "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/ipfs-car": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/ipfs-car/-/ipfs-car-0.7.0.tgz", - "integrity": "sha512-9ser6WWZ1ZMTCGbcVkRXUzOrpQ4SIiLfzIEnk+3LQsXbV09yeZg3ijhRuEXozEIYE68Go9JmOFshamsK9iKlNQ==", - "license": "(Apache-2.0 AND MIT)", + "node_modules/ipfs-core-types": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.14.1.tgz", + "integrity": "sha512-4ujF8NlM9bYi2I6AIqPP9wfGGX0x/gRCkMoFdOQfxxrFg6HcAdfS+0/irK8mp4e7znOHWReOHeWqCGw+dAPwsw==", + "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", + "license": "Apache-2.0 OR MIT", "dependencies": { - "@ipld/car": "^3.2.3", - "@web-std/blob": "^3.0.1", - "bl": "^5.0.0", - "blockstore-core": "^1.0.2", - "browser-readablestream-to-it": "^1.0.2", - "idb-keyval": "^6.0.3", - "interface-blockstore": "^2.0.2", - "ipfs-core-types": "^0.8.3", - "ipfs-core-utils": "^0.12.1", - "ipfs-unixfs-exporter": "^7.0.4", - "ipfs-unixfs-importer": "^9.0.4", - "ipfs-utils": "^9.0.2", - "it-all": "^1.0.5", - "it-last": "^1.0.5", - "it-pipe": "^1.1.0", - "meow": "^9.0.0", - "move-file": "^2.1.0", - "multiformats": "^9.6.3", - "stream-to-it": "^0.2.3", - "streaming-iterables": "^6.0.0", - "uint8arrays": "^3.0.0" + "@ipld/dag-pb": "^4.0.0", + "@libp2p/interface-keychain": "^2.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interface-peer-info": "^1.0.2", + "@libp2p/interface-pubsub": "^3.0.0", + "@multiformats/multiaddr": "^11.1.5", + "@types/node": "^18.0.0", + "interface-datastore": "^7.0.0", + "ipfs-unixfs": "^9.0.0", + "multiformats": "^11.0.0" }, - "bin": { - "🚘": "dist/cjs/cli/cli.js", - "ipfs-car": "dist/cjs/cli/cli.js" + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/ipfs-car/node_modules/any-signal": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-2.1.2.tgz", - "integrity": "sha512-B+rDnWasMi/eWcajPcCWSlYc7muXOrcYrqgyzcdKisl2H/WTlQ0gip1KyQfr0ZlxJdsuWCj/LWwQm7fhyhRfIQ==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "native-abort-controller": "^1.0.3" - } - }, - "node_modules/ipfs-car/node_modules/blob-to-it": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/blob-to-it/-/blob-to-it-1.0.4.tgz", - "integrity": "sha512-iCmk0W4NdbrWgRRuxOriU8aM5ijeVLI61Zulsmg/lUHNr7pYjoj+U77opLefNagevtrrbMt3JQ5Qip7ar178kA==", - "license": "ISC", - "dependencies": { - "browser-readablestream-to-it": "^1.0.3" - } - }, - "node_modules/ipfs-car/node_modules/browser-readablestream-to-it": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-1.0.3.tgz", - "integrity": "sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/interface-datastore": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-6.1.1.tgz", - "integrity": "sha512-AmCS+9CT34pp2u0QQVXjKztkuq3y5T+BIciuiHDDtDZucZD8VudosnSdUyXJV6IsRkN5jc4RFDhCk1O6Q3Gxjg==", - "license": "MIT", - "dependencies": { - "interface-store": "^2.0.2", - "nanoid": "^3.0.2", - "uint8arrays": "^3.0.0" - } - }, - "node_modules/ipfs-car/node_modules/interface-store": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-2.0.2.tgz", - "integrity": "sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg==", - "license": "(Apache-2.0 OR MIT)" - }, - "node_modules/ipfs-car/node_modules/ipfs-core-types": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.8.4.tgz", - "integrity": "sha512-sbRZA1QX3xJ6ywTiVQZMOxhlhp4osAZX2SXx3azOLxAtxmGWDMkHYt722VV4nZ2GyJy8qyk5GHQIZ0uvQnpaTg==", - "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", - "license": "(Apache-2.0 OR MIT)", - "dependencies": { - "interface-datastore": "^6.0.2", - "multiaddr": "^10.0.0", - "multiformats": "^9.4.13" - } - }, - "node_modules/ipfs-car/node_modules/ipfs-core-utils": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/ipfs-core-utils/-/ipfs-core-utils-0.12.2.tgz", - "integrity": "sha512-RfxP3rPhXuqKIUmTAUhmee6fmaV3A7LMnjOUikRKpSyqESz/DR7aGK7tbttMxkZdkSEr0rFXlqbyb0vVwmn0wQ==", - "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", - "license": "MIT", - "dependencies": { - "any-signal": "^2.1.2", - "blob-to-it": "^1.0.1", - "browser-readablestream-to-it": "^1.0.1", - "debug": "^4.1.1", - "err-code": "^3.0.1", - "ipfs-core-types": "^0.8.4", - "ipfs-unixfs": "^6.0.3", - "ipfs-utils": "^9.0.2", - "it-all": "^1.0.4", - "it-map": "^1.0.4", - "it-peekable": "^1.0.2", - "it-to-stream": "^1.0.0", - "merge-options": "^3.0.4", - "multiaddr": "^10.0.0", - "multiaddr-to-uri": "^8.0.0", - "multiformats": "^9.4.13", - "nanoid": "^3.1.23", - "parse-duration": "^1.0.0", - "timeout-abort-controller": "^1.1.1", - "uint8arrays": "^3.0.0" - } - }, - "node_modules/ipfs-car/node_modules/ipfs-unixfs": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz", - "integrity": "sha512-0DQ7p0/9dRB6XCb0mVCTli33GzIzSVx5udpJuVM47tGcD+W+Bl4LsnoLswd3ggNnNEakMv1FdoFITiEnchXDqQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "err-code": "^3.0.1", - "protobufjs": "^6.10.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-car/node_modules/it-all": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", - "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/it-last": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-last/-/it-last-1.0.6.tgz", - "integrity": "sha512-aFGeibeiX/lM4bX3JY0OkVCFkAw8+n9lkukkLNivbJRvNz8lI3YXv5xcqhFUV2lDJiraEK3OXRDbGuevnnR67Q==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/it-map": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-1.0.6.tgz", - "integrity": "sha512-XT4/RM6UHIFG9IobGlQPFQUrlEKkU4eBUFG3qhWhfAdh1JfF2x11ShCrKCdmZ0OiZppPfoLuzcfA4cey6q3UAQ==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/it-peekable": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-1.0.3.tgz", - "integrity": "sha512-5+8zemFS+wSfIkSZyf0Zh5kNN+iGyccN02914BY4w/Dj+uoFEoPSvj5vaWn8pNZJNSxzjW0zHRxC3LUb2KWJTQ==", - "license": "ISC" - }, - "node_modules/ipfs-car/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" - }, - "node_modules/ipfs-car/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, - "node_modules/ipfs-car/node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/ipfs-car/node_modules/protobufjs": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", - "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/ipfs-car/node_modules/retimer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-2.0.0.tgz", - "integrity": "sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg==", - "license": "MIT" - }, - "node_modules/ipfs-car/node_modules/timeout-abort-controller": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/timeout-abort-controller/-/timeout-abort-controller-1.1.1.tgz", - "integrity": "sha512-BsF9i3NAJag6T0ZEjki9j654zoafI2X6ayuNd6Tp8+Ul6Tr5s4jo973qFeiWrRSweqvskC+AHDKUmIW4b7pdhQ==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "retimer": "^2.0.0" - } - }, - "node_modules/ipfs-car/node_modules/uint8arrays": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", - "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", - "license": "MIT", - "dependencies": { - "multiformats": "^9.4.2" - } - }, - "node_modules/ipfs-core-types": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.14.1.tgz", - "integrity": "sha512-4ujF8NlM9bYi2I6AIqPP9wfGGX0x/gRCkMoFdOQfxxrFg6HcAdfS+0/irK8mp4e7znOHWReOHeWqCGw+dAPwsw==", - "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@ipld/dag-pb": "^4.0.0", - "@libp2p/interface-keychain": "^2.0.0", - "@libp2p/interface-peer-id": "^2.0.0", - "@libp2p/interface-peer-info": "^1.0.2", - "@libp2p/interface-pubsub": "^3.0.0", - "@multiformats/multiaddr": "^11.1.5", - "@types/node": "^18.0.0", - "interface-datastore": "^7.0.0", - "ipfs-unixfs": "^9.0.0", - "multiformats": "^11.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-core-types/node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "node_modules/ipfs-core-types/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -5262,136 +4626,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", - "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-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "dependencies": { - "multiformats": "^9.4.2" - } - }, - "node_modules/ipfs-unixfs-importer": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-9.0.10.tgz", - "integrity": "sha512-W+tQTVcSmXtFh7FWYWwPBGXJ1xDgREbIyI1E5JzDcimZLIyT5gGMfxR3oKPxxWj+GKMpP5ilvMQrbsPzWcm3Fw==", - "license": "Apache-2.0 OR MIT", - "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": { - "@ipld/dag-pb": "^2.0.2", - "@multiformats/murmur3": "^1.0.3", - "bl": "^5.0.0", - "err-code": "^3.0.1", - "hamt-sharding": "^2.0.0", - "interface-blockstore": "^2.0.3", - "ipfs-unixfs": "^6.0.0", - "it-all": "^1.0.5", - "it-batch": "^1.0.8", - "it-first": "^1.0.6", - "it-parallel-batch": "^1.0.9", - "merge-options": "^3.0.4", - "multiformats": "^9.4.2", - "rabin-wasm": "^0.1.4", - "uint8arrays": "^3.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/@ipld/dag-pb": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-2.1.18.tgz", - "integrity": "sha512-ZBnf2fuX9y3KccADURG5vb9FaOeMjFkCrNysB0PtftME/4iCTjxfaLoNq/IAh5fTqUOMXvryN6Jyka4ZGuMLIg==", - "license": "(Apache-2.0 AND MIT)", - "dependencies": { - "multiformats": "^9.5.4" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/ipfs-unixfs": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz", - "integrity": "sha512-0DQ7p0/9dRB6XCb0mVCTli33GzIzSVx5udpJuVM47tGcD+W+Bl4LsnoLswd3ggNnNEakMv1FdoFITiEnchXDqQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "err-code": "^3.0.1", - "protobufjs": "^6.10.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/it-all": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", - "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==", - "license": "ISC" - }, - "node_modules/ipfs-unixfs-importer/node_modules/it-first": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/it-first/-/it-first-1.0.7.tgz", - "integrity": "sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==", - "license": "ISC" - }, - "node_modules/ipfs-unixfs-importer/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" - }, - "node_modules/ipfs-unixfs-importer/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, - "node_modules/ipfs-unixfs-importer/node_modules/protobufjs": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", - "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/ipfs-unixfs-importer/node_modules/uint8arrays": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", - "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", "license": "MIT", "dependencies": { "multiformats": "^9.4.2" @@ -5447,31 +4681,6 @@ "url": "https://github.com/sponsors/ai" } ], - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "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": "BSD-3-Clause" - }, - "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==", - "dev": true, "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" @@ -5740,8 +4949,6 @@ }, "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": { @@ -6878,12 +6085,24 @@ "node": ">=8" } }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -6995,8 +6214,6 @@ }, "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" @@ -7004,8 +6221,6 @@ }, "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" @@ -7060,8 +6275,6 @@ }, "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" @@ -7128,8 +6341,6 @@ }, "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" @@ -7137,8 +6348,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" @@ -7252,8 +6461,6 @@ }, "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/multer": { @@ -7442,8 +6649,6 @@ }, "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" @@ -7648,8 +6853,6 @@ }, "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" @@ -7657,8 +6860,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" @@ -7669,8 +6870,6 @@ }, "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" @@ -7681,8 +6880,6 @@ }, "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" @@ -7854,8 +7051,6 @@ }, "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" @@ -7898,8 +7093,6 @@ }, "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", @@ -7913,9 +7106,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -8063,8 +7256,6 @@ }, "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", @@ -8106,8 +7297,6 @@ }, "node_modules/qs": { "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -8156,8 +7345,6 @@ }, "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" @@ -8165,8 +7352,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", @@ -8329,6 +7514,27 @@ "node": ">=8" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/require-addon": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", @@ -8422,8 +7628,6 @@ }, "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", @@ -8475,8 +7679,6 @@ }, "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/semver": { @@ -8491,8 +7693,6 @@ }, "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", @@ -8517,8 +7717,6 @@ }, "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", @@ -8553,8 +7751,6 @@ }, "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": { @@ -8602,8 +7798,6 @@ }, "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", @@ -8621,8 +7815,6 @@ }, "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", @@ -8637,8 +7829,6 @@ }, "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", @@ -8655,8 +7845,6 @@ }, "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", @@ -8811,10 +7999,14 @@ "node": ">=10" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, "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" @@ -8840,6 +8032,26 @@ "node": ">=18.0.0" } }, + "node_modules/stellar-sdk/node_modules/@stellar/stellar-base": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-13.1.0.tgz", + "integrity": "sha512-90EArG+eCCEzDGj3OJNoCtwpWDwxjv+rs/RNPhvg4bulpjN/CSRj+Ys/SalRcfM4/WRC5/qAfjzmJBAuquWhkA==", + "license": "Apache-2.0", + "dependencies": { + "@stellar/js-xdr": "^3.1.2", + "base32.js": "^0.1.0", + "bignumber.js": "^9.1.2", + "buffer": "^6.0.3", + "sha.js": "^2.3.6", + "tweetnacl": "^1.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "sodium-native": "^4.3.3" + } + }, "node_modules/stream-to-it": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-0.2.4.tgz", @@ -8963,37 +8175,36 @@ } }, "node_modules/superagent": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", - "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", - "deprecated": "Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "dev": true, "license": "MIT", "dependencies": { - "component-emitter": "^1.3.0", + "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^3.5.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0" + "qs": "^6.14.1" }, "engines": { "node": ">=14.18.0" } }, "node_modules/supertest": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", - "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", - "deprecated": "Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "dev": true, "license": "MIT", "dependencies": { + "cookie-signature": "^1.2.2", "methods": "^1.1.2", - "superagent": "^9.0.1" + "superagent": "^10.3.0" }, "engines": { "node": ">=14.18.0" @@ -9099,8 +8310,6 @@ }, "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" @@ -9174,8 +8383,6 @@ }, "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", @@ -9294,15 +8501,13 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, "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" @@ -9403,8 +8608,6 @@ }, "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" @@ -9527,6 +8730,34 @@ "w3name": "^1.0.6" } }, + "node_modules/web3.storage/node_modules/@ipld/dag-pb": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-2.1.18.tgz", + "integrity": "sha512-ZBnf2fuX9y3KccADURG5vb9FaOeMjFkCrNysB0PtftME/4iCTjxfaLoNq/IAh5fTqUOMXvryN6Jyka4ZGuMLIg==", + "license": "(Apache-2.0 AND MIT)", + "dependencies": { + "multiformats": "^9.5.4" + } + }, + "node_modules/web3.storage/node_modules/any-signal": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-2.1.2.tgz", + "integrity": "sha512-B+rDnWasMi/eWcajPcCWSlYc7muXOrcYrqgyzcdKisl2H/WTlQ0gip1KyQfr0ZlxJdsuWCj/LWwQm7fhyhRfIQ==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "native-abort-controller": "^1.0.3" + } + }, + "node_modules/web3.storage/node_modules/blob-to-it": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/blob-to-it/-/blob-to-it-1.0.4.tgz", + "integrity": "sha512-iCmk0W4NdbrWgRRuxOriU8aM5ijeVLI61Zulsmg/lUHNr7pYjoj+U77opLefNagevtrrbMt3JQ5Qip7ar178kA==", + "license": "ISC", + "dependencies": { + "browser-readablestream-to-it": "^1.0.3" + } + }, "node_modules/web3.storage/node_modules/browser-readablestream-to-it": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-1.0.3.tgz", @@ -9542,12 +8773,240 @@ "cborg": "cli.js" } }, + "node_modules/web3.storage/node_modules/interface-datastore": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-6.1.1.tgz", + "integrity": "sha512-AmCS+9CT34pp2u0QQVXjKztkuq3y5T+BIciuiHDDtDZucZD8VudosnSdUyXJV6IsRkN5jc4RFDhCk1O6Q3Gxjg==", + "license": "MIT", + "dependencies": { + "interface-store": "^2.0.2", + "nanoid": "^3.0.2", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/web3.storage/node_modules/interface-store": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-2.0.2.tgz", + "integrity": "sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg==", + "license": "(Apache-2.0 OR MIT)" + }, + "node_modules/web3.storage/node_modules/ipfs-car": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/ipfs-car/-/ipfs-car-0.7.0.tgz", + "integrity": "sha512-9ser6WWZ1ZMTCGbcVkRXUzOrpQ4SIiLfzIEnk+3LQsXbV09yeZg3ijhRuEXozEIYE68Go9JmOFshamsK9iKlNQ==", + "license": "(Apache-2.0 AND MIT)", + "dependencies": { + "@ipld/car": "^3.2.3", + "@web-std/blob": "^3.0.1", + "bl": "^5.0.0", + "blockstore-core": "^1.0.2", + "browser-readablestream-to-it": "^1.0.2", + "idb-keyval": "^6.0.3", + "interface-blockstore": "^2.0.2", + "ipfs-core-types": "^0.8.3", + "ipfs-core-utils": "^0.12.1", + "ipfs-unixfs-exporter": "^7.0.4", + "ipfs-unixfs-importer": "^9.0.4", + "ipfs-utils": "^9.0.2", + "it-all": "^1.0.5", + "it-last": "^1.0.5", + "it-pipe": "^1.1.0", + "meow": "^9.0.0", + "move-file": "^2.1.0", + "multiformats": "^9.6.3", + "stream-to-it": "^0.2.3", + "streaming-iterables": "^6.0.0", + "uint8arrays": "^3.0.0" + }, + "bin": { + "🚘": "dist/cjs/cli/cli.js", + "ipfs-car": "dist/cjs/cli/cli.js" + } + }, + "node_modules/web3.storage/node_modules/ipfs-core-types": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.8.4.tgz", + "integrity": "sha512-sbRZA1QX3xJ6ywTiVQZMOxhlhp4osAZX2SXx3azOLxAtxmGWDMkHYt722VV4nZ2GyJy8qyk5GHQIZ0uvQnpaTg==", + "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", + "license": "(Apache-2.0 OR MIT)", + "dependencies": { + "interface-datastore": "^6.0.2", + "multiaddr": "^10.0.0", + "multiformats": "^9.4.13" + } + }, + "node_modules/web3.storage/node_modules/ipfs-core-utils": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/ipfs-core-utils/-/ipfs-core-utils-0.12.2.tgz", + "integrity": "sha512-RfxP3rPhXuqKIUmTAUhmee6fmaV3A7LMnjOUikRKpSyqESz/DR7aGK7tbttMxkZdkSEr0rFXlqbyb0vVwmn0wQ==", + "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", + "license": "MIT", + "dependencies": { + "any-signal": "^2.1.2", + "blob-to-it": "^1.0.1", + "browser-readablestream-to-it": "^1.0.1", + "debug": "^4.1.1", + "err-code": "^3.0.1", + "ipfs-core-types": "^0.8.4", + "ipfs-unixfs": "^6.0.3", + "ipfs-utils": "^9.0.2", + "it-all": "^1.0.4", + "it-map": "^1.0.4", + "it-peekable": "^1.0.2", + "it-to-stream": "^1.0.0", + "merge-options": "^3.0.4", + "multiaddr": "^10.0.0", + "multiaddr-to-uri": "^8.0.0", + "multiformats": "^9.4.13", + "nanoid": "^3.1.23", + "parse-duration": "^1.0.0", + "timeout-abort-controller": "^1.1.1", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/web3.storage/node_modules/ipfs-unixfs": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz", + "integrity": "sha512-0DQ7p0/9dRB6XCb0mVCTli33GzIzSVx5udpJuVM47tGcD+W+Bl4LsnoLswd3ggNnNEakMv1FdoFITiEnchXDqQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "err-code": "^3.0.1", + "protobufjs": "^6.10.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/web3.storage/node_modules/ipfs-unixfs-importer": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-9.0.10.tgz", + "integrity": "sha512-W+tQTVcSmXtFh7FWYWwPBGXJ1xDgREbIyI1E5JzDcimZLIyT5gGMfxR3oKPxxWj+GKMpP5ilvMQrbsPzWcm3Fw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@ipld/dag-pb": "^2.0.2", + "@multiformats/murmur3": "^1.0.3", + "bl": "^5.0.0", + "err-code": "^3.0.1", + "hamt-sharding": "^2.0.0", + "interface-blockstore": "^2.0.3", + "ipfs-unixfs": "^6.0.0", + "it-all": "^1.0.5", + "it-batch": "^1.0.8", + "it-first": "^1.0.6", + "it-parallel-batch": "^1.0.9", + "merge-options": "^3.0.4", + "multiformats": "^9.4.2", + "rabin-wasm": "^0.1.4", + "uint8arrays": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/web3.storage/node_modules/it-all": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", + "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==", + "license": "ISC" + }, + "node_modules/web3.storage/node_modules/it-first": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-1.0.7.tgz", + "integrity": "sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==", + "license": "ISC" + }, + "node_modules/web3.storage/node_modules/it-last": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-last/-/it-last-1.0.6.tgz", + "integrity": "sha512-aFGeibeiX/lM4bX3JY0OkVCFkAw8+n9lkukkLNivbJRvNz8lI3YXv5xcqhFUV2lDJiraEK3OXRDbGuevnnR67Q==", + "license": "ISC" + }, + "node_modules/web3.storage/node_modules/it-map": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-map/-/it-map-1.0.6.tgz", + "integrity": "sha512-XT4/RM6UHIFG9IobGlQPFQUrlEKkU4eBUFG3qhWhfAdh1JfF2x11ShCrKCdmZ0OiZppPfoLuzcfA4cey6q3UAQ==", + "license": "ISC" + }, + "node_modules/web3.storage/node_modules/it-peekable": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-1.0.3.tgz", + "integrity": "sha512-5+8zemFS+wSfIkSZyf0Zh5kNN+iGyccN02914BY4w/Dj+uoFEoPSvj5vaWn8pNZJNSxzjW0zHRxC3LUb2KWJTQ==", + "license": "ISC" + }, + "node_modules/web3.storage/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/web3.storage/node_modules/multiformats": { "version": "9.9.0", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", "license": "(Apache-2.0 AND MIT)" }, + "node_modules/web3.storage/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/web3.storage/node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/web3.storage/node_modules/retimer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/retimer/-/retimer-2.0.0.tgz", + "integrity": "sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg==", + "license": "MIT" + }, + "node_modules/web3.storage/node_modules/timeout-abort-controller": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/timeout-abort-controller/-/timeout-abort-controller-1.1.1.tgz", + "integrity": "sha512-BsF9i3NAJag6T0ZEjki9j654zoafI2X6ayuNd6Tp8+Ul6Tr5s4jo973qFeiWrRSweqvskC+AHDKUmIW4b7pdhQ==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "retimer": "^2.0.0" + } + }, "node_modules/web3.storage/node_modules/uint8arrays": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", @@ -9630,8 +9089,6 @@ }, "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/write-file-atomic": { diff --git a/package.json b/package.json index c88fe70..fc95fa3 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "ethers": "^6.8.1", "express": "^5.2.1", "form-data": "^4.0.0", + "ioredis": "^5.6.1", "ipfs-http-client": "^60.0.1", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", diff --git a/rateLimiter.test.js b/rateLimiter.test.js new file mode 100644 index 0000000..24f2ff4 --- /dev/null +++ b/rateLimiter.test.js @@ -0,0 +1,443 @@ +/** + * Tests for the Leaky Bucket rate limiter, Sybil analysis, and middleware. + * + * Uses a simple in-memory Redis mock so the suite runs without a real Redis + * instance. + */ + +const { + LeakyBucketRateLimiter, +} = require("./src/services/leakyBucketRateLimiter"); +const { SybilAnalysisService } = require("./src/services/sybilAnalysisService"); +const { + createRateLimiter, + extractWallet, +} = require("./middleware/rateLimiter"); + +// --------------------------------------------------------------------------- +// Minimal Redis mock (supports only the commands the services actually use) +// --------------------------------------------------------------------------- +class RedisMock { + constructor() { + this.store = new Map(); // key -> string value + this.hashes = new Map(); // key -> Map(field -> value) + this.sortedSets = new Map(); // key -> Map(member -> score) + this.ttls = new Map(); // key -> expiry timestamp + } + + // --- strings --- + async get(key) { + this._evict(key); + return this.store.get(key) ?? null; + } + async set(key, value, ...args) { + this.store.set(key, String(value)); + if (args[0] === "EX" && typeof args[1] === "number") { + this.ttls.set(key, Date.now() + args[1] * 1000); + } + return "OK"; + } + async incr(key) { + const cur = parseInt(this.store.get(key) || "0", 10); + const next = cur + 1; + this.store.set(key, String(next)); + return next; + } + async del(...keys) { + let count = 0; + for (const k of keys) { + if (this.store.delete(k)) count++; + if (this.hashes.delete(k)) count++; + if (this.sortedSets.delete(k)) count++; + this.ttls.delete(k); + } + return count; + } + async ttl(key) { + const ex = this.ttls.get(key); + if (!ex) return -1; + const remaining = Math.ceil((ex - Date.now()) / 1000); + return remaining > 0 ? remaining : -2; + } + async expire(key, seconds) { + this.ttls.set(key, Date.now() + seconds * 1000); + return 1; + } + + // --- hashes --- + async hmset(key, obj) { + if (!this.hashes.has(key)) this.hashes.set(key, new Map()); + const h = this.hashes.get(key); + for (const [f, v] of Object.entries(obj)) h.set(f, String(v)); + return "OK"; + } + async hmget(key, ...fields) { + const h = this.hashes.get(key); + return fields.map((f) => (h ? (h.get(f) ?? null) : null)); + } + async hgetall(key) { + const h = this.hashes.get(key); + if (!h) return {}; + const result = {}; + for (const [f, v] of h) result[f] = v; + return result; + } + + // --- sorted sets --- + async zadd(key, score, member) { + if (!this.sortedSets.has(key)) this.sortedSets.set(key, new Map()); + this.sortedSets.get(key).set(member, score); + return 1; + } + async zscore(key, member) { + const ss = this.sortedSets.get(key); + if (!ss) return null; + const s = ss.get(member); + return s !== undefined ? String(s) : null; + } + async zrevrange(key, start, stop, withScores) { + const ss = this.sortedSets.get(key); + if (!ss) return []; + const entries = [...ss.entries()] + .sort((a, b) => b[1] - a[1]) + .slice(start, stop + 1); + const result = []; + for (const [member, score] of entries) { + result.push(member); + if (withScores === "WITHSCORES") result.push(String(score)); + } + return result; + } + async zrem(key, member) { + const ss = this.sortedSets.get(key); + if (!ss) return 0; + return ss.delete(member) ? 1 : 0; + } + + // --- eval (Lua emulation) --- + async eval(script, numkeys, key, capacity, leakRate, now) { + capacity = Number(capacity); + leakRate = Number(leakRate); + now = Number(now); + + const [levelStr, lastDripStr] = await this.hmget(key, "level", "lastDrip"); + let level = parseFloat(levelStr) || 0; + const lastDrip = parseFloat(lastDripStr) || now; + + const elapsed = now - lastDrip; + const leaked = elapsed * leakRate; + level = Math.max(0, level - leaked); + + if (level + 1 > capacity) { + await this.hmset(key, { level: String(level), lastDrip: String(now) }); + return [0, String(level), String(capacity)]; + } + + level = level + 1; + await this.hmset(key, { level: String(level), lastDrip: String(now) }); + return [1, String(level), String(capacity)]; + } + + // TTL eviction helper + _evict(key) { + const ex = this.ttls.get(key); + if (ex && Date.now() > ex) { + this.store.delete(key); + this.hashes.delete(key); + this.ttls.delete(key); + } + } +} + +// --------------------------------------------------------------------------- +// LeakyBucketRateLimiter +// --------------------------------------------------------------------------- +describe("LeakyBucketRateLimiter", () => { + let redis; + let limiter; + + beforeEach(() => { + redis = new RedisMock(); + limiter = new LeakyBucketRateLimiter(redis, { + bucketCapacity: 5, + leakRatePerSecond: 1, + blockDurationSeconds: 10, + sybilThreshold: 2, + }); + }); + + it("allows requests within capacity", async () => { + const result = await limiter.consume("WALLET_A"); + expect(result.allowed).toBe(true); + expect(result.blocked).toBe(false); + expect(result.currentLevel).toBeGreaterThan(0); + }); + + it("rejects and blocks when bucket overflows", async () => { + // Fill bucket to capacity. + for (let i = 0; i < 5; i++) { + const r = await limiter.consume("WALLET_B"); + expect(r.allowed).toBe(true); + } + + // Next request should be rejected. + const rejected = await limiter.consume("WALLET_B"); + expect(rejected.allowed).toBe(false); + expect(rejected.blocked).toBe(true); + expect(rejected.retryAfterSeconds).toBe(10); + }); + + it("returns blocked status on subsequent attempts while blocked", async () => { + for (let i = 0; i < 5; i++) await limiter.consume("WALLET_C"); + await limiter.consume("WALLET_C"); // triggers block + + const attempt = await limiter.consume("WALLET_C"); + expect(attempt.allowed).toBe(false); + expect(attempt.blocked).toBe(true); + }); + + it("tracks violations", async () => { + for (let i = 0; i < 5; i++) await limiter.consume("WALLET_D"); + const overflow = await limiter.consume("WALLET_D"); + expect(overflow.violations).toBe(1); + + const count = await limiter.getViolationCount("WALLET_D"); + expect(count).toBe(1); + }); + + it("isBlocked returns correct state", async () => { + expect(await limiter.isBlocked("WALLET_E")).toBe(false); + for (let i = 0; i < 5; i++) await limiter.consume("WALLET_E"); + await limiter.consume("WALLET_E"); + expect(await limiter.isBlocked("WALLET_E")).toBe(true); + }); + + it("unblock clears the block", async () => { + for (let i = 0; i < 5; i++) await limiter.consume("WALLET_F"); + await limiter.consume("WALLET_F"); + expect(await limiter.isBlocked("WALLET_F")).toBe(true); + + await limiter.unblock("WALLET_F"); + expect(await limiter.isBlocked("WALLET_F")).toBe(false); + }); + + it("reset clears all state", async () => { + for (let i = 0; i < 5; i++) await limiter.consume("WALLET_G"); + await limiter.consume("WALLET_G"); + await limiter.reset("WALLET_G"); + + expect(await limiter.isBlocked("WALLET_G")).toBe(false); + expect(await limiter.getViolationCount("WALLET_G")).toBe(0); + + const fresh = await limiter.consume("WALLET_G"); + expect(fresh.allowed).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// SybilAnalysisService +// --------------------------------------------------------------------------- +describe("SybilAnalysisService", () => { + let redis; + let sybil; + + beforeEach(() => { + redis = new RedisMock(); + sybil = new SybilAnalysisService(redis, { flagThreshold: 3 }); + }); + + it("does not flag when below threshold", async () => { + const result = await sybil.evaluate("WALLET_1", 2); + expect(result.flagged).toBe(false); + }); + + it("flags when violations meet threshold", async () => { + const result = await sybil.evaluate("WALLET_2", 3, { + endpoint: "/api/cdn/token", + ip: "1.2.3.4", + }); + expect(result.flagged).toBe(true); + expect(await sybil.isFlagged("WALLET_2")).toBe(true); + }); + + it("stores detail metadata", async () => { + await sybil.evaluate("WALLET_3", 5, { + endpoint: "/api/test", + ip: "10.0.0.1", + }); + const detail = await sybil.getDetail("WALLET_3"); + expect(detail).not.toBeNull(); + expect(detail.violations).toBe("5"); + expect(detail.lastEndpoint).toBe("/api/test"); + expect(detail.lastIp).toBe("10.0.0.1"); + }); + + it("getTopFlagged returns ranked wallets", async () => { + await sybil.evaluate("w1", 3); + await sybil.evaluate("w2", 10); + await sybil.evaluate("w3", 5); + + const top = await sybil.getTopFlagged(2); + expect(top).toHaveLength(2); + expect(top[0].wallet).toBe("w2"); + expect(top[0].violations).toBe(10); + }); + + it("unflag removes the wallet from flagged set", async () => { + await sybil.evaluate("WALLET_4", 4); + expect(await sybil.isFlagged("WALLET_4")).toBe(true); + await sybil.unflag("WALLET_4"); + expect(await sybil.isFlagged("WALLET_4")).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// extractWallet helper +// --------------------------------------------------------------------------- +describe("extractWallet", () => { + it("prefers req.user.address", () => { + const req = { + user: { address: "0xABC" }, + body: { walletAddress: "0xOther" }, + query: {}, + }; + expect(extractWallet(req)).toBe("0xABC"); + }); + + it("falls back to req.user.publicKey", () => { + const req = { user: { publicKey: "GPUBKEY" }, body: {}, query: {} }; + expect(extractWallet(req)).toBe("GPUBKEY"); + }); + + it("falls back to req.body.walletAddress", () => { + const req = { body: { walletAddress: "0xBody" }, query: {} }; + expect(extractWallet(req)).toBe("0xBody"); + }); + + it("falls back to query params", () => { + const req = { body: {}, query: { publicKey: "GQUERY" } }; + expect(extractWallet(req)).toBe("GQUERY"); + }); + + it("returns null when no wallet is present", () => { + const req = { body: {}, query: {} }; + expect(extractWallet(req)).toBeNull(); + }); +}); + +// --------------------------------------------------------------------------- +// createRateLimiter middleware +// --------------------------------------------------------------------------- +describe("createRateLimiter middleware", () => { + let redis; + let middleware; + + const mockRes = () => { + const res = { + _status: null, + _json: null, + _headers: {}, + status(code) { + res._status = code; + return res; + }, + json(body) { + res._json = body; + return res; + }, + set(key, value) { + res._headers[key] = value; + }, + }; + return res; + }; + + beforeEach(() => { + redis = new RedisMock(); + middleware = createRateLimiter({ + redis, + bucketCapacity: 3, + leakRatePerSecond: 1, + blockDurationSeconds: 10, + sybilThreshold: 2, + }); + }); + + it("throws if no redis client is provided", () => { + expect(() => createRateLimiter({})).toThrow("redis client"); + }); + + it("passes through when no wallet is identified", async () => { + const req = { body: {}, query: {} }; + const res = mockRes(); + const next = jest.fn(); + await middleware(req, res, next); + expect(next).toHaveBeenCalled(); + }); + + it("allows requests within the limit", async () => { + const req = { + user: { address: "0xFan" }, + body: {}, + query: {}, + originalUrl: "/api/test", + }; + const res = mockRes(); + const next = jest.fn(); + await middleware(req, res, next); + expect(next).toHaveBeenCalled(); + expect(res._headers["X-RateLimit-Limit"]).toBeDefined(); + }); + + it("returns 429 when rate limit is exceeded", async () => { + for (let i = 0; i < 3; i++) { + const req = { + user: { address: "0xBot" }, + body: {}, + query: {}, + originalUrl: "/api/x", + }; + await middleware(req, mockRes(), jest.fn()); + } + + const req = { + user: { address: "0xBot" }, + body: {}, + query: {}, + originalUrl: "/api/x", + }; + const res = mockRes(); + const next = jest.fn(); + await middleware(req, res, next); + + expect(next).not.toHaveBeenCalled(); + expect(res._status).toBe(429); + expect(res._json.error).toMatch(/Rate limit exceeded/); + expect(res._headers["Retry-After"]).toBeDefined(); + }); + + it("fails open when Redis throws", async () => { + const brokenRedis = { + get: () => { + throw new Error("connection refused"); + }, + eval: () => { + throw new Error("connection refused"); + }, + }; + + const mw = createRateLimiter({ redis: brokenRedis, bucketCapacity: 5 }); + const req = { + user: { address: "0xOops" }, + body: {}, + query: {}, + originalUrl: "/api/y", + }; + const res = mockRes(); + const next = jest.fn(); + await mw(req, res, next); + + // Should call next() instead of crashing. + expect(next).toHaveBeenCalled(); + }); +}); diff --git a/src/config/redis.js b/src/config/redis.js new file mode 100644 index 0000000..2892cd8 --- /dev/null +++ b/src/config/redis.js @@ -0,0 +1,65 @@ +const Redis = require("ioredis"); + +let redisClient = null; + +/** + * Create or return the singleton Redis client. + * + * Supports configuration via environment variables: + * REDIS_URL – full connection string (e.g. redis://user:pass@host:6379) + * REDIS_HOST – hostname (default 127.0.0.1) + * REDIS_PORT – port (default 6379) + * REDIS_PASSWORD – password (optional) + * REDIS_DB – database index (default 0) + * + * @param {object} [opts] Override options forwarded to ioredis. + * @returns {import('ioredis').Redis} + */ +function getRedisClient(opts = {}) { + if (redisClient) return redisClient; + + const url = process.env.REDIS_URL; + + if (url) { + redisClient = new Redis(url, { + maxRetriesPerRequest: 3, + retryStrategy: (times) => Math.min(times * 200, 5000), + ...opts, + }); + } else { + redisClient = new Redis({ + host: process.env.REDIS_HOST || "127.0.0.1", + port: Number(process.env.REDIS_PORT || 6379), + password: process.env.REDIS_PASSWORD || undefined, + db: Number(process.env.REDIS_DB || 0), + maxRetriesPerRequest: 3, + retryStrategy: (times) => Math.min(times * 200, 5000), + ...opts, + }); + } + + redisClient.on("error", (err) => { + console.error("[Redis] connection error:", err.message); + }); + + return redisClient; +} + +/** + * Gracefully close the Redis connection (e.g. during shutdown). + */ +async function closeRedisClient() { + if (redisClient) { + await redisClient.quit(); + redisClient = null; + } +} + +/** + * Replace the singleton – useful for injecting a mock in tests. + */ +function setRedisClient(client) { + redisClient = client; +} + +module.exports = { getRedisClient, closeRedisClient, setRedisClient }; diff --git a/src/services/leakyBucketRateLimiter.js b/src/services/leakyBucketRateLimiter.js new file mode 100644 index 0000000..02d3405 --- /dev/null +++ b/src/services/leakyBucketRateLimiter.js @@ -0,0 +1,187 @@ +/** + * Leaky Bucket Rate Limiter backed by Redis. + * + * The algorithm models a bucket that: + * - Has a fixed capacity (burst size). + * - Leaks at a constant rate (requests / second). + * - Each incoming request adds 1 unit to the bucket. + * - If the bucket is full the request is rejected. + * + * All state is stored in Redis so the limiter works across multiple + * server instances. + * + * Redis keys used per wallet: + * ratelimit:{wallet} – hash { level, lastDrip } + * ratelimit:blocked:{wallet} – string with TTL (temporary block) + */ + +const BUCKET_KEY_PREFIX = "ratelimit:"; +const BLOCK_KEY_PREFIX = "ratelimit:blocked:"; + +// Lua script executed atomically in Redis. +// Returns: [allowed (0|1), currentLevel, bucketCapacity] +const LEAKY_BUCKET_LUA = ` +local key = KEYS[1] +local capacity = tonumber(ARGV[1]) +local leakRate = tonumber(ARGV[2]) +local now = tonumber(ARGV[3]) + +local data = redis.call('HMGET', key, 'level', 'lastDrip') +local level = tonumber(data[1]) or 0 +local lastDrip = tonumber(data[2]) or now + +-- Leak tokens since the last request +local elapsed = now - lastDrip +local leaked = elapsed * leakRate +level = math.max(0, level - leaked) + +-- Try to add the new request +if level + 1 > capacity then + -- Bucket full – reject + redis.call('HMSET', key, 'level', level, 'lastDrip', now) + redis.call('EXPIRE', key, math.ceil(capacity / leakRate) + 60) + return {0, tostring(level), tostring(capacity)} +end + +level = level + 1 +redis.call('HMSET', key, 'level', level, 'lastDrip', now) +redis.call('EXPIRE', key, math.ceil(capacity / leakRate) + 60) +return {1, tostring(level), tostring(capacity)} +`; + +class LeakyBucketRateLimiter { + /** + * @param {import('ioredis').Redis} redisClient + * @param {object} [options] + * @param {number} [options.bucketCapacity=60] Max burst size. + * @param {number} [options.leakRatePerSecond=1] Tokens leaked per second. + * @param {number} [options.blockDurationSeconds=300] How long to block after overflow (5 min). + * @param {number} [options.sybilThreshold=3] Consecutive blocks before Sybil flagging. + */ + constructor(redisClient, options = {}) { + this.redis = redisClient; + this.bucketCapacity = options.bucketCapacity ?? 60; + this.leakRatePerSecond = options.leakRatePerSecond ?? 1; + this.blockDurationSeconds = options.blockDurationSeconds ?? 300; + this.sybilThreshold = options.sybilThreshold ?? 3; + } + + /** + * Consume one token for the given wallet. + * + * @param {string} wallet Wallet address (Stellar public key or Ethereum address). + * @returns {Promise<{allowed: boolean, blocked: boolean, currentLevel: number, + * capacity: number, retryAfterSeconds: number | null}>} + */ + async consume(wallet) { + const normalizedWallet = wallet.toLowerCase(); + const blockKey = `${BLOCK_KEY_PREFIX}${normalizedWallet}`; + + // 1. Check if wallet is already temporarily blocked. + const blocked = await this.redis.get(blockKey); + if (blocked) { + const ttl = await this.redis.ttl(blockKey); + return { + allowed: false, + blocked: true, + currentLevel: this.bucketCapacity, + capacity: this.bucketCapacity, + retryAfterSeconds: ttl > 0 ? ttl : this.blockDurationSeconds, + }; + } + + // 2. Run the atomic leaky-bucket script. + const bucketKey = `${BUCKET_KEY_PREFIX}${normalizedWallet}`; + const nowSeconds = Date.now() / 1000; + + const [allowed, levelStr, capacityStr] = await this.redis.eval( + LEAKY_BUCKET_LUA, + 1, + bucketKey, + this.bucketCapacity, + this.leakRatePerSecond, + nowSeconds, + ); + + const currentLevel = parseFloat(levelStr); + const capacity = parseFloat(capacityStr); + + if (allowed === 1) { + return { + allowed: true, + blocked: false, + currentLevel, + capacity, + retryAfterSeconds: null, + }; + } + + // 3. Bucket overflow – impose temporary block and increment violation counter. + await this.redis.set(blockKey, "1", "EX", this.blockDurationSeconds); + + const violationKey = `ratelimit:violations:${normalizedWallet}`; + const violations = await this.redis.incr(violationKey); + // Expire violations counter after 24 hours so old infractions don't persist forever. + await this.redis.expire(violationKey, 86400); + + return { + allowed: false, + blocked: true, + currentLevel, + capacity, + retryAfterSeconds: this.blockDurationSeconds, + violations, + }; + } + + /** + * Return the current violation count for a wallet. + * + * @param {string} wallet + * @returns {Promise} + */ + async getViolationCount(wallet) { + const count = await this.redis.get( + `ratelimit:violations:${wallet.toLowerCase()}`, + ); + return count ? parseInt(count, 10) : 0; + } + + /** + * Check whether a wallet is currently blocked. + * + * @param {string} wallet + * @returns {Promise} + */ + async isBlocked(wallet) { + const res = await this.redis.get( + `${BLOCK_KEY_PREFIX}${wallet.toLowerCase()}`, + ); + return res !== null; + } + + /** + * Manually unblock a wallet (admin action). + * + * @param {string} wallet + */ + async unblock(wallet) { + await this.redis.del(`${BLOCK_KEY_PREFIX}${wallet.toLowerCase()}`); + } + + /** + * Reset all rate-limit state for a wallet. + * + * @param {string} wallet + */ + async reset(wallet) { + const w = wallet.toLowerCase(); + await this.redis.del( + `${BUCKET_KEY_PREFIX}${w}`, + `${BLOCK_KEY_PREFIX}${w}`, + `ratelimit:violations:${w}`, + ); + } +} + +module.exports = { LeakyBucketRateLimiter }; diff --git a/src/services/sybilAnalysisService.js b/src/services/sybilAnalysisService.js new file mode 100644 index 0000000..c820bad --- /dev/null +++ b/src/services/sybilAnalysisService.js @@ -0,0 +1,129 @@ +/** + * Sybil Analysis Service + * + * Tracks wallets that repeatedly exceed rate limits and flags them for + * review. Flagged wallets are stored in a Redis sorted set keyed by + * violation count so operators can query the worst offenders first. + * + * Redis structures: + * sybil:flagged – sorted set (score = violation count, member = wallet) + * sybil:details:{wallet} – hash with detailed flag metadata + */ + +const FLAGGED_SET_KEY = "sybil:flagged"; +const DETAIL_KEY_PREFIX = "sybil:details:"; + +class SybilAnalysisService { + /** + * @param {import('ioredis').Redis} redisClient + * @param {object} [options] + * @param {number} [options.flagThreshold=3] Violations required for flagging. + * @param {number} [options.detailTtlSeconds=604800] How long detail records persist (7 days). + */ + constructor(redisClient, options = {}) { + this.redis = redisClient; + this.flagThreshold = options.flagThreshold ?? 3; + this.detailTtlSeconds = options.detailTtlSeconds ?? 604800; + } + + /** + * Evaluate a wallet after a rate-limit violation and flag it if the + * threshold has been reached. + * + * @param {string} wallet + * @param {number} violations Current cumulative violation count. + * @param {object} [meta] Extra metadata (IP, endpoint, etc.). + * @returns {Promise<{flagged: boolean, violations: number}>} + */ + async evaluate(wallet, violations, meta = {}) { + const normalizedWallet = wallet.toLowerCase(); + + if (violations < this.flagThreshold) { + return { flagged: false, violations }; + } + + // Add / update in the sorted set (score = violation count). + await this.redis.zadd(FLAGGED_SET_KEY, violations, normalizedWallet); + + // Store granular details for investigation. + const detailKey = `${DETAIL_KEY_PREFIX}${normalizedWallet}`; + await this.redis.hmset(detailKey, { + wallet: normalizedWallet, + violations: String(violations), + flaggedAt: new Date().toISOString(), + lastEndpoint: meta.endpoint || "", + lastIp: meta.ip || "", + reason: "Exceeded rate-limit violation threshold", + }); + await this.redis.expire(detailKey, this.detailTtlSeconds); + + console.warn( + `[SybilAnalysis] Wallet ${normalizedWallet} flagged – ${violations} violations`, + ); + + return { flagged: true, violations }; + } + + /** + * Check whether a wallet was previously flagged. + * + * @param {string} wallet + * @returns {Promise} + */ + async isFlagged(wallet) { + const score = await this.redis.zscore( + FLAGGED_SET_KEY, + wallet.toLowerCase(), + ); + return score !== null; + } + + /** + * Retrieve the detail record for a flagged wallet. + * + * @param {string} wallet + * @returns {Promise} + */ + async getDetail(wallet) { + const data = await this.redis.hgetall( + `${DETAIL_KEY_PREFIX}${wallet.toLowerCase()}`, + ); + return data && Object.keys(data).length > 0 ? data : null; + } + + /** + * Return the top N most-violated wallets. + * + * @param {number} [count=20] + * @returns {Promise>} + */ + async getTopFlagged(count = 20) { + const results = await this.redis.zrevrange( + FLAGGED_SET_KEY, + 0, + count - 1, + "WITHSCORES", + ); + const entries = []; + for (let i = 0; i < results.length; i += 2) { + entries.push({ + wallet: results[i], + violations: parseInt(results[i + 1], 10), + }); + } + return entries; + } + + /** + * Remove a wallet from the flagged set (admin clearance). + * + * @param {string} wallet + */ + async unflag(wallet) { + const w = wallet.toLowerCase(); + await this.redis.zrem(FLAGGED_SET_KEY, w); + await this.redis.del(`${DETAIL_KEY_PREFIX}${w}`); + } +} + +module.exports = { SybilAnalysisService };