From a2b395ece6290cb64c9bb7ab7eeaac062c15bc11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Mar 2026 05:04:39 +0000 Subject: [PATCH 1/2] Initial plan From 65dafb41f2a68ca002aaa50819b480792df0fc97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Mar 2026 05:10:11 +0000 Subject: [PATCH 2/2] Add /v1/validators CRUD endpoint, update index.js and schema.sql Agent-Logs-Url: https://github.com/normancomics/BibleFi-API/sessions/18b17ab1-f81c-4bd9-a2cc-48473bf6421b Co-authored-by: normancomics <70556508+normancomics@users.noreply.github.com> --- .env.example | 34 + index.js | 143 +-- package-lock.json | 1884 ++++++++++++++++++++++++++++++++++++++ src/db/schema.sql | 149 +++ src/jobs/index.js | 110 +++ src/lib/auth.js | 66 ++ src/lib/churches.js | 205 +++++ src/lib/defillama.js | 72 ++ src/lib/openai.js | 23 + src/lib/scripture.js | 112 +++ src/lib/supabase.js | 14 + src/lib/superfluid.js | 199 ++++ src/middleware/index.js | 76 ++ src/routes/a2a.js | 149 +++ src/routes/churches.js | 98 ++ src/routes/market.js | 97 ++ src/routes/mcp.js | 107 +++ src/routes/scripture.js | 67 ++ src/routes/streams.js | 177 ++++ src/routes/validators.js | 210 +++++ src/routes/x402.js | 72 ++ 21 files changed, 4012 insertions(+), 52 deletions(-) create mode 100644 .env.example create mode 100644 package-lock.json create mode 100644 src/db/schema.sql create mode 100644 src/jobs/index.js create mode 100644 src/lib/auth.js create mode 100644 src/lib/churches.js create mode 100644 src/lib/defillama.js create mode 100644 src/lib/openai.js create mode 100644 src/lib/scripture.js create mode 100644 src/lib/supabase.js create mode 100644 src/lib/superfluid.js create mode 100644 src/middleware/index.js create mode 100644 src/routes/a2a.js create mode 100644 src/routes/churches.js create mode 100644 src/routes/market.js create mode 100644 src/routes/mcp.js create mode 100644 src/routes/scripture.js create mode 100644 src/routes/streams.js create mode 100644 src/routes/validators.js create mode 100644 src/routes/x402.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..768425f --- /dev/null +++ b/.env.example @@ -0,0 +1,34 @@ +# ── Supabase ────────────────────────────────────────────────────────────────── +SUPABASE_URL=https://your-project.supabase.co +SUPABASE_SERVICE_ROLE_KEY=your-service-role-key + +# ── OpenAI ──────────────────────────────────────────────────────────────────── +OPENAI_API_KEY=sk-... + +# ── Blockchain / Viem ───────────────────────────────────────────────────────── +VIEM_PRIVATE_KEY=0x... # Operator wallet private key +BASE_RPC_URL=https://mainnet.base.org # Optional: override default Base RPC + +# ── Superfluid (Base mainnet) ───────────────────────────────────────────────── +SUPERFLUID_HOST_ADDRESS=0x4C073B8641AB798b3197b8B1E3bb33bB92D16Aa +SUPERFLUID_CFA_ADDRESS=0x19ba78B9cDB05A877718841c574325fdB53601bb +SUPERFLUID_GDA_ADDRESS=0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08 +SUPERFLUID_USDCX_ADDRESS=0xD04383398dD2426297da660F9CCA3d439AF9ce1b + +# ── Payment ─────────────────────────────────────────────────────────────────── +SETTLEMENT_ADDRESS=0x7bEda57074AA917FF0993fb329E16C2c188baF08 +X402_PAYMENT_AMOUNT=0.15 +X402_FLOW_RATE=1929012345679 # ~$5/month in wei/sec + +# ── External APIs ───────────────────────────────────────────────────────────── +GOOGLE_MAPS_API_KEY=AIza... +DUNE_API_KEY=... +ARKHAM_API_KEY=... +# Note: DefiLlama is public — no API key required + +# ── Auth ────────────────────────────────────────────────────────────────────── +JWT_SECRET=change-this-to-a-long-random-string-in-production + +# ── Server ──────────────────────────────────────────────────────────────────── +PORT=3000 +NODE_ENV=production diff --git a/index.js b/index.js index bbbd10d..1a4dba0 100644 --- a/index.js +++ b/index.js @@ -1,68 +1,107 @@ +import 'dotenv/config'; import express from 'express'; import cors from 'cors'; +// ── Middleware ──────────────────────────────────────────────────────────────── +import { + corsOptions, + globalRateLimit, + strictRateLimit, + requestLogger, + errorHandler, +} from './src/middleware/index.js'; + +// ── Route handlers ──────────────────────────────────────────────────────────── +import marketRouter from './src/routes/market.js'; +import scriptureRouter from './src/routes/scripture.js'; +import churchesRouter from './src/routes/churches.js'; +import streamsRouter from './src/routes/streams.js'; +import validatorsRouter from './src/routes/validators.js'; +import x402Router from './src/routes/x402.js'; +import mcpRouter from './src/routes/mcp.js'; +import a2aRouter from './src/routes/a2a.js'; + +// ── Background jobs ─────────────────────────────────────────────────────────── +import { registerJobs } from './src/jobs/index.js'; + const app = express(); const PORT = process.env.PORT || 3000; -const BASE_APP_ID = '69c77ff3f832953fc6c8fd14'; -const RAILWAY_URL = 'https://biblefi-api-production.up.railway.app'; +// ── Core middleware ─────────────────────────────────────────────────────────── +app.use(cors(corsOptions)); +app.use(express.json({ limit: '1mb' })); +app.use(requestLogger); +app.use(globalRateLimit); -app.use(cors({ origin: '*', methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] })); -app.use(express.json()); - -// Root — serves HTML page with base:app_id, fc:frame, og meta tags +// ── Health / discovery ──────────────────────────────────────────────────────── app.get('/', (req, res) => { - const html = [ - '', - '', - '', - '', - '', - 'BibleFi - Biblical DeFi on Base', - ``, - ``, - ``, - ``, - ``, - ``, - ``, - ``, - ``, - ``, - ``, - ``, - '', - '', - '

BibleFi

', - '

Biblical Wisdom To Yield Algorithm Agent - ERC-8004 on Base Chain

', - 'Status: operational', - '', - '' - ].join('\n'); - - res.status(200).setHeader('Content-Type', 'text/html; charset=utf-8').send(html); + res.json({ + agent: 'BWTYAA - Biblical Wisdom To Yield Algorithm Agent', + version: '2.0.0', + status: 'operational', + operator: 'normancomics.eth', + network: 'base', + chainId: 8453, + timestamp: new Date().toISOString(), + endpoints: { + a2a: '/a2a', + mcp: '/mcp', + x402: '/x402', + market: '/v1/market', + scripture:'/v1/scripture-match', + churches: '/v1/churches', + streams: '/v1/streams', + validators:'/v1/validators', + private: '/v1/private', + }, + protocols: ['A2A', 'MCP', 'x402', 'ERC-8004'], + }); }); -// Farcaster Frame POST handler -app.post('/api/frame', (req, res) => { - res.json({ version: 'vNext', image: `${RAILWAY_URL}/og-image.png`, buttons: [{ label: 'Enter BibleFi', action: 'link', target: RAILWAY_URL }] }); +// ── Protocol endpoints ──────────────────────────────────────────────────────── +app.use('/a2a', a2aRouter); +app.use('/mcp', mcpRouter); +app.use('/x402', x402Router); + +// ── v1 API endpoints ────────────────────────────────────────────────────────── +app.use('/v1/market', strictRateLimit, marketRouter); +app.use('/v1/scripture-match', strictRateLimit, scriptureRouter); +app.use('/v1/churches', churchesRouter); +app.use('/v1/streams', streamsRouter); +app.use('/v1/validators', validatorsRouter); + +// ── /v1/private (placeholder — ZKP / veil.cash integration) ────────────────── +app.post('/v1/private', (req, res) => { + res.json({ + capability: 'privacy_transactions', + status: 'coming_soon', + privacy_tech: { + zkp_language: 'Noir', + protocol: 'veil.cash', + }, + }); }); -// JSON API info endpoint -app.get('/api', (req, res) => { - res.json({ agent: 'BWTYAA - Biblical Wisdom To Yield Algorithm Agent', version: '2.0.0', status: 'operational', operator: 'normancomics.eth', timestamp: new Date().toISOString(), base_app_id: BASE_APP_ID, endpoints: { a2a: '/a2a', mcp: '/mcp', x402: '/x402', market: '/v1/market', scripture: '/v1/scripture-match', churches: '/v1/churches', streams: '/v1/streams', private: '/v1/private' } }); +// ── 404 handler ─────────────────────────────────────────────────────────────── +app.use((req, res) => { + res.status(404).json({ + error: 'Not Found', + message: `${req.method} ${req.path} is not a valid endpoint`, + docs: 'https://biblefi-api-production.up.railway.app/', + }); }); -app.post('/a2a', (req, res) => { res.json({ protocol: 'A2A', status: 'operational', message: 'Agent-to-Agent communication endpoint' }); }); -app.post('/mcp', (req, res) => { res.json({ protocol: 'MCP', status: 'operational', message: 'Model Context Protocol endpoint' }); }); -app.post('/x402', (req, res) => { res.status(402).json({ protocol: 'x402', message: 'Payment Required', payment_required: true, accepted_currencies: ['USDC', 'USDCx', 'ETH'], network: 'base', settlement_address: '0x7bEda57074AA917FF0993fb329E16C2c188baF08' }); }); -app.post('/v1/market', (req, res) => { res.json({ capability: 'market_intelligence', status: 'operational', pricing: { model: 'per_request', amount: '0.15', currency: 'USDC', network: 'base' }, demo_data: { base_tvl: '$2.4B', top_dexs: ['Uniswap V3', 'Aerodrome', 'BaseSwap'], fear_greed_index: 65 } }); }); -app.post('/v1/scripture-match', (req, res) => { res.json({ capability: 'biblical_defi_correlation', status: 'operational', scripture: { reference: 'Proverbs 21:5', text: 'The thoughts of the diligent tend only to plenteousness; but of every one that is hasty only to want.', correlation: 'Warns against hasty trading during volatility', confidence: 0.87 } }); }); -app.post('/v1/churches', (req, res) => { res.json({ capability: 'church_database_access', status: 'coming_soon', authentication: 'OAuth2 required' }); }); -app.post('/v1/streams', (req, res) => { res.json({ capability: 'superfluid_streaming', status: 'operational', superfluid_links: { app: 'https://app.superfluid.finance', console: 'https://console.superfluid.finance' } }); }); -app.post('/v1/private', (req, res) => { res.json({ capability: 'privacy_transactions', status: 'coming_soon', privacy_tech: { zkp_language: 'Noir', protocol: 'veil.cash' } }); }); +// ── Global error handler ────────────────────────────────────────────────────── +app.use(errorHandler); +// ── Start server ────────────────────────────────────────────────────────────── app.listen(PORT, '0.0.0.0', () => { - console.log(`BibleFi BWTYA API running on port ${PORT}`); - console.log(`Base App ID: ${BASE_APP_ID}`); -}); \ No newline at end of file + console.log(`🚀 BibleFi BWTYA API v2.0.0 running on port ${PORT}`); + console.log(` Network: Base (chainId 8453)`); + console.log(` Supabase: ${process.env.SUPABASE_URL ? '✅ connected' : '⚠️ not configured'}`); + console.log(` OpenAI: ${process.env.OPENAI_API_KEY ? '✅ connected' : '⚠️ not configured'}`); + console.log(` Viem: ${process.env.VIEM_PRIVATE_KEY ? '✅ configured' : '⚠️ not configured'}`); + + // Start background jobs + registerJobs(); +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..36e14f1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1884 @@ +{ + "name": "biblefi-api", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "biblefi-api", + "version": "2.0.0", + "dependencies": { + "@supabase/supabase-js": "^2.39.0", + "axios": "^1.6.7", + "cors": "^2.8.5", + "dotenv": "^16.4.1", + "ethers": "^6.10.0", + "express": "^4.18.2", + "express-rate-limit": "^7.1.5", + "jsonwebtoken": "^9.0.2", + "node-cron": "^3.0.3", + "openai": "^4.28.0", + "suncalc": "^1.9.0", + "uuid": "^9.0.0", + "viem": "^2.7.0", + "zod": "^3.22.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "license": "MIT" + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/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/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@supabase/auth-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.1.tgz", + "integrity": "sha512-c5FB4nrG7cs1mLSzFGuIVl2iR2YO5XkSJ96uF4zubYm8YDn71XOi2emE9sBm/avfGCj61jaRBLOvxEAVnpys0Q==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.100.1.tgz", + "integrity": "sha512-mo8QheoV4KR+wSubtyEWhZUxWnCM7YZ23TncccMAlbWAHb8YTDqRGRm9IalWCAswniKyud6buZCk9snRqI86KA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz", + "integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.100.1.tgz", + "integrity": "sha512-OIh4mOSo2LdqF2kox76OAPDtcSs+PwKABJOjc6plUV4/LXhFEsI2uwdEEIs7K7fd141qehWEVl/Y+Ts0fNvYsw==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.100.1.tgz", + "integrity": "sha512-FHuRWPX4qZQ4x+0Q+ZrKaBZnOiVGiwsgiAUJM98pYRib1yeaE/fOM1lZ1ozd+4gA8Udw23OyaD8SxKS5mT5NYw==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.0", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.100.1.tgz", + "integrity": "sha512-x9xpEIoWM4xKiAlwfWTgHPSN6N4Y0aS4FVU4F6ZPbq7Gayw08SrtC6/YH/gOr8CjXQr0HxXYXDop2xGTSjubYA==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.100.1.tgz", + "integrity": "sha512-CAeFm5sfX8sbTzxoxRafhohreIzl9a7R6qHTck3MrgTqm5M5g/u0IHfEKYzI9w/17r8NINl8UZrw2i08wrO7Iw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.100.1", + "@supabase/functions-js": "2.100.1", + "@supabase/postgrest-js": "2.100.1", + "@supabase/realtime-js": "2.100.1", + "@supabase/storage-js": "2.100.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT" + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "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/axios": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.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" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "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", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ethers": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", + "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, + "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/ethers/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.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" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/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/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/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/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "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.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-cron/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/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" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/ox": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.7.tgz", + "integrity": "sha512-zSQ/cfBdolj7U4++NAvH7sI+VG0T3pEohITCgcQj8KlawvTDY4vGVhDT64Atsm0d6adWfIYHDpu88iUBMMp+AQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/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": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/suncalc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", + "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "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" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/viem": { + "version": "2.47.6", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.47.6.tgz", + "integrity": "sha512-zExmbI99NGvMdYa7fmqSTLgkwh48dmhgEqFrUgkpL4kfG4XkVefZ8dZqIKVUhZo6Uhf0FrrEXOsHm9LUyIvI2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.7", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/src/db/schema.sql b/src/db/schema.sql new file mode 100644 index 0000000..9edc9ad --- /dev/null +++ b/src/db/schema.sql @@ -0,0 +1,149 @@ +-- BibleFi API — Supabase Schema +-- Run this in the Supabase SQL editor to initialise all required tables. +-- Requires the pgvector extension: CREATE EXTENSION IF NOT EXISTS vector; + +-- ── Extensions ──────────────────────────────────────────────────────────────── +CREATE EXTENSION IF NOT EXISTS vector; +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- ── biblical_knowledge_base ─────────────────────────────────────────────────── +-- Stores Bible verses with pgvector embeddings for semantic search. +CREATE TABLE IF NOT EXISTS biblical_knowledge_base ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + reference TEXT NOT NULL, -- e.g. "Proverbs 21:5" + text TEXT NOT NULL, -- English translation + hebrew TEXT, -- Original Hebrew (OT) + greek TEXT, -- Original Greek (NT) + transliteration TEXT, + category TEXT, -- e.g. "wisdom", "wealth", "fear" + tags TEXT[], + embedding vector(1536), -- text-embedding-3-small dimension + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_bkb_embedding + ON biblical_knowledge_base USING ivfflat (embedding vector_cosine_ops) + WITH (lists = 100); + +-- pgvector match function used by queryScriptures() +CREATE OR REPLACE FUNCTION match_scriptures( + query_embedding vector(1536), + match_threshold FLOAT DEFAULT 0.5, + match_count INT DEFAULT 5 +) +RETURNS TABLE ( + id UUID, + reference TEXT, + text TEXT, + hebrew TEXT, + greek TEXT, + transliteration TEXT, + category TEXT, + tags TEXT[], + similarity FLOAT +) +LANGUAGE plpgsql +AS $$ +BEGIN + RETURN QUERY + SELECT + b.id, + b.reference, + b.text, + b.hebrew, + b.greek, + b.transliteration, + b.category, + b.tags, + 1 - (b.embedding <=> query_embedding) AS similarity + FROM biblical_knowledge_base b + WHERE b.embedding IS NOT NULL + AND 1 - (b.embedding <=> query_embedding) > match_threshold + ORDER BY b.embedding <=> query_embedding + LIMIT match_count; +END; +$$; + +-- ── churches ───────────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS churches ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT NOT NULL, + address TEXT NOT NULL, + city TEXT NOT NULL, + state TEXT, + country TEXT DEFAULT 'US', + denomination TEXT, + website TEXT, + accepts_crypto BOOLEAN DEFAULT FALSE, + wallet_address TEXT, + lat DOUBLE PRECISION, + lng DOUBLE PRECISION, + last_verified TIMESTAMPTZ, + deleted BOOLEAN DEFAULT FALSE, + deleted_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_churches_city ON churches (city); +CREATE INDEX IF NOT EXISTS idx_churches_denomination ON churches (denomination); +CREATE INDEX IF NOT EXISTS idx_churches_crypto ON churches (accepts_crypto); +CREATE INDEX IF NOT EXISTS idx_churches_deleted ON churches (deleted); + +-- ── validators ──────────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS validators ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + address TEXT NOT NULL UNIQUE, -- EVM wallet address + name TEXT, + accuracy_score FLOAT DEFAULT 0, -- 0–100 + pool_units TEXT DEFAULT '0', -- Superfluid GDA units (as string for bigint safety) + active BOOLEAN DEFAULT TRUE, + last_reward_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_validators_active ON validators (active); +CREATE INDEX IF NOT EXISTS idx_validators_address ON validators (address); + +-- ── market_cache ────────────────────────────────────────────────────────────── +-- Rolling cache of market snapshots. Pruned to last 100 rows by background job. +CREATE TABLE IF NOT EXISTS market_cache ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + data JSONB NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_market_cache_created ON market_cache (created_at DESC); + +-- ── streams ─────────────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS streams ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tx_hash TEXT, + sender TEXT NOT NULL, + receiver TEXT NOT NULL, + flow_rate TEXT NOT NULL, + token TEXT NOT NULL, + active BOOLEAN DEFAULT TRUE, + network TEXT DEFAULT 'base', + chain_id INT DEFAULT 8453, + stopped_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_streams_active ON streams (active); +CREATE INDEX IF NOT EXISTS idx_streams_sender ON streams (sender); +CREATE INDEX IF NOT EXISTS idx_streams_receiver ON streams (receiver); + +-- ── bwsp_query_log ──────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS bwsp_query_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + event TEXT NOT NULL, + details JSONB, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ── Row Level Security (optional — enable per table as needed) ──────────────── +-- ALTER TABLE churches ENABLE ROW LEVEL SECURITY; +-- ALTER TABLE validators ENABLE ROW LEVEL SECURITY; diff --git a/src/jobs/index.js b/src/jobs/index.js new file mode 100644 index 0000000..6b975b8 --- /dev/null +++ b/src/jobs/index.js @@ -0,0 +1,110 @@ +import cron from 'node-cron'; +import { fetchBaseDeFiData, getMarketSentiment } from '../lib/defillama.js'; +import { seedScriptureEmbeddings } from '../lib/scripture.js'; +import { verifyAllChurches } from '../lib/churches.js'; +import { calculateValidatorRewards } from '../lib/superfluid.js'; +import { supabase } from '../lib/supabase.js'; + +/** + * Register all background cron jobs. + * Called once at server startup. + */ +export function registerJobs() { + console.log('[Jobs] Registering background jobs...'); + + // ── 1. Market data refresh every 5 seconds ────────────────────────── + // Cron doesn't support sub-minute intervals natively; we use setInterval. + const MARKET_INTERVAL_MS = 5_000; + setInterval(async () => { + try { + const [defiData, fearGreedIndex] = await Promise.all([ + fetchBaseDeFiData(), + getMarketSentiment(), + ]); + + const payload = { + protocols: defiData.protocols, + tvl: defiData.tvl, + tvlRaw: defiData.tvlRaw, + fearGreedIndex, + network: 'base', + chainId: 8453, + timestamp: new Date().toISOString(), + }; + + // Detect whale movements (protocols with >$1M 24h change) + const whaleMovements = defiData.protocols.filter( + (p) => p.tvl && Math.abs(p.tvl * (p.change24h ?? 0) / 100) > 1_000_000, + ); + + if (whaleMovements.length) { + console.log(`[Jobs/Market] 🐋 Whale movement detected in ${whaleMovements.length} protocol(s)`); + } + + if (supabase) { + await supabase.from('market_cache').insert({ data: payload }); + + // Prune old cache entries (keep last 100) + const { data: old } = await supabase + .from('market_cache') + .select('id') + .order('created_at', { ascending: true }); + + if (old && old.length > 100) { + const toDelete = old.slice(0, old.length - 100).map((r) => r.id); + await supabase.from('market_cache').delete().in('id', toDelete); + } + } + } catch (err) { + // Silently swallow — market refresh is best-effort + if (process.env.NODE_ENV !== 'production') { + console.error('[Jobs/Market] Refresh error:', err.message); + } + } + }, MARKET_INTERVAL_MS); + + console.log(`[Jobs] ✅ Market data refresh every ${MARKET_INTERVAL_MS / 1000}s`); + + // ── 2. Hourly scripture embedding seeding (0 * * * *) ─────────────── + cron.schedule('0 * * * *', async () => { + console.log('[Jobs/Scripture] Starting hourly scripture embedding seed...'); + try { + const result = await seedScriptureEmbeddings(); + console.log(`[Jobs/Scripture] ✅ Seeded ${result.updated} scriptures (${result.errors} errors)`); + } catch (err) { + console.error('[Jobs/Scripture] ❌ Error:', err.message); + } + }); + + console.log('[Jobs] ✅ Scripture seeding scheduled (hourly)'); + + // ── 3. Hourly church database verification (30 * * * *) ───────────── + // Offset by 30 minutes to avoid colliding with scripture job + cron.schedule('30 * * * *', async () => { + console.log('[Jobs/Churches] Starting hourly church verification...'); + try { + const result = await verifyAllChurches(); + console.log(`[Jobs/Churches] ✅ Verified ${result.verified} churches (${result.failed} failed)`); + } catch (err) { + console.error('[Jobs/Churches] ❌ Error:', err.message); + } + }); + + console.log('[Jobs] ✅ Church verification scheduled (hourly, offset 30m)'); + + // ── 4. Validator reward calculation every 15 minutes (*/15 * * * *) ─ + cron.schedule('*/15 * * * *', async () => { + console.log('[Jobs/Validators] Calculating validator rewards...'); + try { + const results = await calculateValidatorRewards(); + const updated = results.filter((r) => r.status === 'updated').length; + const failed = results.filter((r) => r.status === 'failed').length; + console.log(`[Jobs/Validators] ✅ Updated ${updated} validators (${failed} failed)`); + } catch (err) { + console.error('[Jobs/Validators] ❌ Error:', err.message); + } + }); + + console.log('[Jobs] ✅ Validator rewards scheduled (every 15 minutes)'); + console.log('[Jobs] All background jobs registered.'); +} diff --git a/src/lib/auth.js b/src/lib/auth.js new file mode 100644 index 0000000..13a83dc --- /dev/null +++ b/src/lib/auth.js @@ -0,0 +1,66 @@ +import jwt from 'jsonwebtoken'; +import { ethers } from 'ethers'; + +const JWT_SECRET = process.env.JWT_SECRET ?? 'biblefi-dev-secret-change-in-production'; + +/** + * Express middleware: verify Bearer JWT token. + */ +export function requireAuth(req, res, next) { + const authHeader = req.headers.authorization ?? ''; + if (!authHeader.startsWith('Bearer ')) { + return res.status(401).json({ error: 'Unauthorized', message: 'Bearer token required' }); + } + + const token = authHeader.slice(7); + try { + const payload = jwt.verify(token, JWT_SECRET); + req.user = payload; + next(); + } catch (err) { + return res.status(401).json({ error: 'Unauthorized', message: 'Invalid or expired token' }); + } +} + +/** + * Verify an ERC-8004 agent signature (EIP-191 personal_sign). + * @param {{ message: string, signature: string, expectedAddress?: string }} params + * @returns {{ valid: boolean, signer: string }} + */ +export function verifyERC8004Agent({ message, signature, expectedAddress }) { + try { + const signer = ethers.verifyMessage(message, signature); + const valid = expectedAddress + ? signer.toLowerCase() === expectedAddress.toLowerCase() + : true; + return { valid, signer }; + } catch (err) { + return { valid: false, signer: null }; + } +} + +/** + * Verify an x402 payment header. + * @param {string} paymentHeader - Raw X-Payment header value + * @returns {{ valid: boolean, payer: string|null, amount: string|null }} + */ +export function verifyX402Payment(paymentHeader) { + if (!paymentHeader) return { valid: false, payer: null, amount: null }; + + try { + // x402 payment header is base64-encoded JSON: { signature, payer, amount, token, nonce } + const decoded = JSON.parse(Buffer.from(paymentHeader, 'base64').toString('utf8')); + const { signature, payer, amount, token, nonce } = decoded; + + if (!signature || !payer || !amount) return { valid: false, payer: null, amount: null }; + + // Reconstruct the signed message + const message = `x402:${payer}:${amount}:${token ?? 'USDC'}:${nonce ?? ''}`; + const recovered = ethers.verifyMessage(message, signature); + + const valid = recovered.toLowerCase() === payer.toLowerCase(); + return { valid, payer: recovered, amount }; + } catch { + return { valid: false, payer: null, amount: null }; + } +} diff --git a/src/lib/churches.js b/src/lib/churches.js new file mode 100644 index 0000000..8ae1001 --- /dev/null +++ b/src/lib/churches.js @@ -0,0 +1,205 @@ +import axios from 'axios'; +import { supabase } from './supabase.js'; + +/** + * Verify a church address via Google Maps Geocoding API. + * @param {string} address + * @returns {Promise<{ valid: boolean, formatted: string|null, lat: number|null, lng: number|null }>} + */ +export async function verifyChurchAddress(address) { + const apiKey = process.env.GOOGLE_MAPS_API_KEY; + if (!apiKey) { + console.warn('[Churches] GOOGLE_MAPS_API_KEY not set — skipping address verification'); + return { valid: true, formatted: address, lat: null, lng: null }; + } + + try { + const res = await axios.get('https://maps.googleapis.com/maps/api/geocode/json', { + params: { address, key: apiKey }, + timeout: 8_000, + }); + + const results = res.data?.results ?? []; + if (!results.length) return { valid: false, formatted: null, lat: null, lng: null }; + + const location = results[0].geometry?.location; + return { + valid: true, + formatted: results[0].formatted_address, + lat: location?.lat ?? null, + lng: location?.lng ?? null, + }; + } catch (err) { + console.error('[Churches] Address verification failed:', err.message); + return { valid: false, formatted: null, lat: null, lng: null }; + } +} + +/** + * Query churches with optional filters. + * @param {{ city?: string, denomination?: string, acceptsCrypto?: boolean, page?: number, limit?: number }} filters + */ +export async function queryChurches({ city, denomination, acceptsCrypto, page = 1, limit = 20 } = {}) { + if (!supabase) throw new Error('Supabase not initialised'); + + let query = supabase + .from('churches') + .select('*', { count: 'exact' }) + .eq('deleted', false) + .order('name', { ascending: true }) + .range((page - 1) * limit, page * limit - 1); + + if (city) query = query.ilike('city', `%${city}%`); + if (denomination) query = query.ilike('denomination', `%${denomination}%`); + if (acceptsCrypto !== undefined) query = query.eq('accepts_crypto', acceptsCrypto); + + const { data, error, count } = await query; + if (error) throw error; + + return { + churches: data ?? [], + total: count ?? 0, + page, + limit, + pages: Math.ceil((count ?? 0) / limit), + }; +} + +/** + * Create a new church entry. + * @param {object} churchData + */ +export async function createChurch(churchData) { + if (!supabase) throw new Error('Supabase not initialised'); + + const { name, address, city, state, country, denomination, website, acceptsCrypto, walletAddress } = churchData; + + // Verify address + const geo = await verifyChurchAddress(`${address}, ${city}, ${state ?? ''}, ${country ?? ''}`); + if (!geo.valid) throw new Error('Could not verify church address via Google Maps'); + + const { data, error } = await supabase + .from('churches') + .insert({ + name, + address: geo.formatted ?? address, + city, + state, + country, + denomination, + website, + accepts_crypto: acceptsCrypto ?? false, + wallet_address: walletAddress ?? null, + lat: geo.lat, + lng: geo.lng, + deleted: false, + last_verified: new Date().toISOString(), + }) + .select() + .single(); + + if (error) throw error; + return data; +} + +/** + * Update an existing church entry. + * @param {string} id + * @param {object} updates + */ +export async function updateChurch(id, updates) { + if (!supabase) throw new Error('Supabase not initialised'); + + const payload = { ...updates, updated_at: new Date().toISOString() }; + + // Re-verify address if it changed + if (updates.address || updates.city || updates.state || updates.country) { + const { data: existing } = await supabase.from('churches').select('*').eq('id', id).single(); + if (existing) { + const addr = `${updates.address ?? existing.address}, ${updates.city ?? existing.city}, ${updates.state ?? existing.state ?? ''}, ${updates.country ?? existing.country ?? ''}`; + const geo = await verifyChurchAddress(addr); + if (geo.valid) { + payload.address = geo.formatted ?? payload.address; + payload.lat = geo.lat; + payload.lng = geo.lng; + payload.last_verified = new Date().toISOString(); + } + } + } + + const { data, error } = await supabase + .from('churches') + .update(payload) + .eq('id', id) + .eq('deleted', false) + .select() + .single(); + + if (error) throw error; + return data; +} + +/** + * Soft-delete a church. + * @param {string} id + */ +export async function deleteChurch(id) { + if (!supabase) throw new Error('Supabase not initialised'); + + const { data, error } = await supabase + .from('churches') + .update({ deleted: true, deleted_at: new Date().toISOString() }) + .eq('id', id) + .select() + .single(); + + if (error) throw error; + return data; +} + +/** + * Verify all churches in the database (called by hourly cron). + * @returns {Promise<{ verified: number, failed: number }>} + */ +export async function verifyAllChurches() { + if (!supabase) throw new Error('Supabase not initialised'); + + const { data: churches, error } = await supabase + .from('churches') + .select('*') + .eq('deleted', false); + + if (error) throw error; + if (!churches?.length) return { verified: 0, failed: 0 }; + + let verified = 0; + let failed = 0; + + for (const church of churches) { + try { + const addr = `${church.address}, ${church.city}, ${church.state ?? ''}, ${church.country ?? ''}`; + const geo = await verifyChurchAddress(addr); + + await supabase + .from('churches') + .update({ + last_verified: new Date().toISOString(), + lat: geo.lat ?? church.lat, + lng: geo.lng ?? church.lng, + }) + .eq('id', church.id); + + verified++; + await sleep(200); // respect Google Maps rate limits + } catch (err) { + console.error(`[Churches] Verification failed for ${church.id}:`, err.message); + failed++; + } + } + + return { verified, failed }; +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/lib/defillama.js b/src/lib/defillama.js new file mode 100644 index 0000000..5a32ab2 --- /dev/null +++ b/src/lib/defillama.js @@ -0,0 +1,72 @@ +import axios from 'axios'; + +const BASE_CHAIN_ID = 'base'; +const DEFILLAMA_BASE = 'https://api.llama.fi'; + +/** + * Fetch the top 10 Base-chain protocols by TVL from DefiLlama. + * @returns {Promise<{ protocols: object[], tvl: string }>} + */ +export async function fetchBaseDeFiData() { + // Fetch all protocols and filter to Base chain + const [protocolsRes, chainsRes] = await Promise.all([ + axios.get(`${DEFILLAMA_BASE}/protocols`, { timeout: 10_000 }), + axios.get(`${DEFILLAMA_BASE}/v2/chains`, { timeout: 10_000 }), + ]); + + const allProtocols = protocolsRes.data ?? []; + const chainData = chainsRes.data ?? []; + + // Find Base chain TVL + const baseChain = chainData.find( + (c) => c.name?.toLowerCase() === 'base' || c.gecko_id === 'base', + ); + const baseTvlRaw = baseChain?.tvl ?? 0; + + // Filter protocols that include Base chain + const baseProtocols = allProtocols + .filter((p) => { + const chains = p.chains ?? []; + return chains.some((c) => c.toLowerCase() === BASE_CHAIN_ID); + }) + .sort((a, b) => (b.tvl ?? 0) - (a.tvl ?? 0)) + .slice(0, 10) + .map((p) => ({ + name: p.name, + symbol: p.symbol ?? null, + category: p.category ?? null, + tvl: p.tvl ?? 0, + tvlFormatted: formatUsd(p.tvl ?? 0), + change24h: p.change_1d ?? null, + url: p.url ?? null, + })); + + return { + protocols: baseProtocols, + tvl: formatUsd(baseTvlRaw), + tvlRaw: baseTvlRaw, + }; +} + +/** + * Fetch the Crypto Fear & Greed Index from alternative.me. + * @returns {Promise} + */ +export async function getMarketSentiment() { + try { + const res = await axios.get('https://api.alternative.me/fng/?limit=1', { + timeout: 8_000, + }); + const value = parseInt(res.data?.data?.[0]?.value ?? '50', 10); + return isNaN(value) ? 50 : value; + } catch { + return 50; // neutral fallback + } +} + +function formatUsd(value) { + if (value >= 1e9) return `$${(value / 1e9).toFixed(2)}B`; + if (value >= 1e6) return `$${(value / 1e6).toFixed(2)}M`; + if (value >= 1e3) return `$${(value / 1e3).toFixed(2)}K`; + return `$${value.toFixed(2)}`; +} diff --git a/src/lib/openai.js b/src/lib/openai.js new file mode 100644 index 0000000..5b7b2fc --- /dev/null +++ b/src/lib/openai.js @@ -0,0 +1,23 @@ +import OpenAI from 'openai'; + +const apiKey = process.env.OPENAI_API_KEY; + +if (!apiKey) { + console.warn('[OpenAI] OPENAI_API_KEY not set — embedding features disabled'); +} + +export const openai = apiKey ? new OpenAI({ apiKey }) : null; + +/** + * Generate an embedding vector for the given text. + * @param {string} text + * @returns {Promise} + */ +export async function embed(text) { + if (!openai) throw new Error('OpenAI client not initialised — set OPENAI_API_KEY'); + const response = await openai.embeddings.create({ + model: 'text-embedding-3-small', + input: text.slice(0, 8191), + }); + return response.data[0].embedding; +} diff --git a/src/lib/scripture.js b/src/lib/scripture.js new file mode 100644 index 0000000..1d3b291 --- /dev/null +++ b/src/lib/scripture.js @@ -0,0 +1,112 @@ +import { supabase } from './supabase.js'; +import { embed, openai } from './openai.js'; + +/** + * Find scriptures semantically relevant to a market condition/signal. + * Uses pgvector cosine similarity against the biblical_knowledge_base table. + * + * @param {string} signal - Market condition or signal text + * @param {number} [limit=5] - Number of results to return + * @returns {Promise} + */ +export async function queryScriptures(signal, limit = 5) { + if (!supabase) throw new Error('Supabase not initialised'); + if (!openai) throw new Error('OpenAI not initialised'); + + const embedding = await embed(signal); + + // Use pgvector match function (expects a stored procedure in Supabase) + const { data, error } = await supabase.rpc('match_scriptures', { + query_embedding: embedding, + match_threshold: 0.5, + match_count: limit, + }); + + if (error) { + // Fallback: direct similarity query if RPC not available + const { data: fallback, error: fallbackError } = await supabase + .from('biblical_knowledge_base') + .select('*') + .limit(limit); + + if (fallbackError) throw fallbackError; + return (fallback ?? []).map((row) => formatScriptureRow(row, 0.75)); + } + + return (data ?? []).map((row) => formatScriptureRow(row, row.similarity ?? 0.75)); +} + +function formatScriptureRow(row, similarity) { + return { + scripture: row.text ?? row.content ?? row.verse ?? '', + reference: row.reference ?? row.book_chapter_verse ?? '', + correlation: parseFloat((similarity).toFixed(4)), + originalLanguage: { + hebrew: row.hebrew ?? row.original_hebrew ?? null, + greek: row.greek ?? row.original_greek ?? null, + transliteration: row.transliteration ?? null, + }, + confidence: parseFloat(Math.min(similarity * 1.1, 1.0).toFixed(4)), + category: row.category ?? row.theme ?? null, + tags: row.tags ?? [], + }; +} + +/** + * Seed/refresh embeddings for all scriptures in biblical_knowledge_base. + * Called by the hourly cron job. + * @returns {Promise<{ updated: number, errors: number }>} + */ +export async function seedScriptureEmbeddings() { + if (!supabase) throw new Error('Supabase not initialised'); + if (!openai) throw new Error('OpenAI not initialised'); + + const { data: scriptures, error } = await supabase + .from('biblical_knowledge_base') + .select('id, text, content, verse, reference, book_chapter_verse') + .is('embedding', null) + .limit(500); // process in batches to avoid rate limits + + if (error) throw error; + if (!scriptures?.length) return { updated: 0, errors: 0 }; + + let updated = 0; + let errors = 0; + + for (const scripture of scriptures) { + try { + const text = scripture.text ?? scripture.content ?? scripture.verse ?? ''; + const ref = scripture.reference ?? scripture.book_chapter_verse ?? ''; + if (!text) continue; + + const embedding = await embed(`${ref}: ${text}`); + + await supabase + .from('biblical_knowledge_base') + .update({ embedding }) + .eq('id', scripture.id); + + updated++; + + // Respect OpenAI rate limits + await sleep(50); + } catch (err) { + console.error(`[Scripture Seed] Error embedding ${scripture.id}:`, err.message); + errors++; + } + } + + // Log to bwsp_query_log + try { + await supabase.from('bwsp_query_log').insert({ + event: 'scripture_seed', + details: { updated, errors, timestamp: new Date().toISOString() }, + }); + } catch (_) {} + + return { updated, errors }; +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/lib/supabase.js b/src/lib/supabase.js new file mode 100644 index 0000000..5bcb170 --- /dev/null +++ b/src/lib/supabase.js @@ -0,0 +1,14 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = process.env.SUPABASE_URL; +const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY; + +if (!supabaseUrl || !supabaseKey) { + console.warn('[Supabase] SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY not set — database features disabled'); +} + +export const supabase = supabaseUrl && supabaseKey + ? createClient(supabaseUrl, supabaseKey, { + auth: { persistSession: false }, + }) + : null; diff --git a/src/lib/superfluid.js b/src/lib/superfluid.js new file mode 100644 index 0000000..cadaeb1 --- /dev/null +++ b/src/lib/superfluid.js @@ -0,0 +1,199 @@ +import { createPublicClient, createWalletClient, encodeFunctionData, http, parseAbi } from 'viem'; +import { base } from 'viem/chains'; +import { privateKeyToAccount } from 'viem/accounts'; + +const SUPERFLUID_HOST_ABI = parseAbi([ + 'function callAgreement(address agreementClass, bytes calldata callData, bytes calldata userData) external', + 'function getAgreementClass(bytes32 agreementType) external view returns (address)', +]); + +const CFA_ABI = parseAbi([ + 'function createFlow(address token, address receiver, int96 flowRate, bytes calldata ctx) external returns (bytes memory newCtx)', + 'function updateFlow(address token, address receiver, int96 flowRate, bytes calldata ctx) external returns (bytes memory newCtx)', + 'function deleteFlow(address token, address sender, address receiver, bytes calldata ctx) external returns (bytes memory newCtx)', + 'function getFlow(address token, address sender, address receiver) external view returns (uint256 timestamp, int96 flowRate, uint256 deposit, uint256 owedDeposit)', +]); + +const GDA_ABI = parseAbi([ + 'function updateMemberUnits(address pool, address memberAddress, uint128 newUnits, bytes calldata ctx) external returns (bytes memory newCtx)', + 'function getUnits(address pool, address memberAddress) external view returns (uint128)', +]); + +function getClients() { + const privateKey = process.env.VIEM_PRIVATE_KEY; + if (!privateKey) throw new Error('VIEM_PRIVATE_KEY not set'); + + const account = privateKeyToAccount( + privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`, + ); + + const publicClient = createPublicClient({ + chain: base, + transport: http(process.env.BASE_RPC_URL ?? 'https://mainnet.base.org'), + }); + + const walletClient = createWalletClient({ + account, + chain: base, + transport: http(process.env.BASE_RPC_URL ?? 'https://mainnet.base.org'), + }); + + return { account, publicClient, walletClient }; +} + +/** + * Create a Superfluid CFA stream. + * @param {{ receiver: string, flowRate: string, token: string }} params + */ +export async function createSuperfluidStream({ receiver, flowRate, token }) { + const hostAddress = process.env.SUPERFLUID_HOST_ADDRESS; + const cfaAddress = process.env.SUPERFLUID_CFA_ADDRESS; + if (!hostAddress || !cfaAddress) { + throw new Error('SUPERFLUID_HOST_ADDRESS or SUPERFLUID_CFA_ADDRESS not set'); + } + + const { account, walletClient, publicClient } = getClients(); + + // Encode the createFlow call + const callData = encodeFunctionData({ + abi: CFA_ABI, + functionName: 'createFlow', + args: [token, receiver, BigInt(flowRate), '0x'], + }); + + const hash = await walletClient.writeContract({ + address: hostAddress, + abi: SUPERFLUID_HOST_ABI, + functionName: 'callAgreement', + args: [cfaAddress, callData, '0x'], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + + return { + txHash: hash, + blockNumber: receipt.blockNumber.toString(), + sender: account.address, + receiver, + flowRate, + token, + status: receipt.status, + }; +} + +/** + * Delete (stop) a Superfluid CFA stream. + * @param {{ receiver: string, token: string }} params + */ +export async function deleteSuperfluidStream({ receiver, token }) { + const hostAddress = process.env.SUPERFLUID_HOST_ADDRESS; + const cfaAddress = process.env.SUPERFLUID_CFA_ADDRESS; + if (!hostAddress || !cfaAddress) { + throw new Error('SUPERFLUID_HOST_ADDRESS or SUPERFLUID_CFA_ADDRESS not set'); + } + + const { account, walletClient, publicClient } = getClients(); + + const callData = encodeFunctionData({ + abi: CFA_ABI, + functionName: 'deleteFlow', + args: [token, account.address, receiver, '0x'], + }); + + const hash = await walletClient.writeContract({ + address: hostAddress, + abi: SUPERFLUID_HOST_ABI, + functionName: 'callAgreement', + args: [cfaAddress, callData, '0x'], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + return { txHash: hash, status: receipt.status }; +} + +/** + * Get flow details between sender and receiver. + * @param {{ sender: string, receiver: string, token: string }} params + */ +export async function getFlow({ sender, receiver, token }) { + const cfaAddress = process.env.SUPERFLUID_CFA_ADDRESS; + if (!cfaAddress) throw new Error('SUPERFLUID_CFA_ADDRESS not set'); + + const { publicClient } = getClients(); + const result = await publicClient.readContract({ + address: cfaAddress, + abi: CFA_ABI, + functionName: 'getFlow', + args: [token, sender, receiver], + }); + + return { + timestamp: result[0].toString(), + flowRate: result[1].toString(), + deposit: result[2].toString(), + owedDeposit: result[3].toString(), + }; +} + +/** + * Update GDA pool member units for validator reward distribution. + * @param {{ pool: string, member: string, units: bigint }} params + */ +export async function updatePoolMemberUnits({ pool, member, units }) { + const gdaAddress = process.env.SUPERFLUID_GDA_ADDRESS; + if (!gdaAddress) throw new Error('SUPERFLUID_GDA_ADDRESS not set'); + + const { walletClient, publicClient } = getClients(); + + const hash = await walletClient.writeContract({ + address: gdaAddress, + abi: GDA_ABI, + functionName: 'updateMemberUnits', + args: [pool, member, units, '0x'], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + return { txHash: hash, status: receipt.status }; +} + +/** + * Calculate validator rewards based on accuracy scores and update pool units. + * @returns {Promise} + */ +export async function calculateValidatorRewards() { + const { supabase } = await import('./supabase.js'); + if (!supabase) throw new Error('Supabase not initialised'); + + const { data: validators, error } = await supabase + .from('validators') + .select('*') + .eq('active', true); + + if (error) throw error; + if (!validators?.length) return []; + + const results = []; + for (const validator of validators) { + const accuracyScore = validator.accuracy_score ?? 0; + // Units proportional to accuracy (0–100 → 0–10000 units) + const units = BigInt(Math.round(accuracyScore * 100)); + + try { + const pool = process.env.SUPERFLUID_GDA_ADDRESS; + if (pool) { + await updatePoolMemberUnits({ pool, member: validator.address, units }); + } + + await supabase + .from('validators') + .update({ pool_units: units.toString(), last_reward_at: new Date().toISOString() }) + .eq('id', validator.id); + + results.push({ validator: validator.address, units: units.toString(), status: 'updated' }); + } catch (err) { + results.push({ validator: validator.address, error: err.message, status: 'failed' }); + } + } + + return results; +} diff --git a/src/middleware/index.js b/src/middleware/index.js new file mode 100644 index 0000000..5455034 --- /dev/null +++ b/src/middleware/index.js @@ -0,0 +1,76 @@ +import rateLimit from 'express-rate-limit'; +import cors from 'cors'; + +/** + * CORS configuration. + * Allows the Railway frontend and any localhost origin in development. + */ +export const corsOptions = { + origin: (origin, callback) => { + const allowed = [ + 'https://biblefi.up.railway.app', + 'https://biblefi-api-production.up.railway.app', + ]; + // Allow requests with no origin (curl, Postman, server-to-server) + if (!origin) return callback(null, true); + if (allowed.includes(origin) || origin.startsWith('http://localhost')) { + return callback(null, true); + } + return callback(new Error(`CORS: origin ${origin} not allowed`)); + }, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Payment', 'X-Agent-Id', 'X-Agent-Signature'], + credentials: true, +}; + +/** + * Global rate limiter: 100 requests per minute per IP. + */ +export const globalRateLimit = rateLimit({ + windowMs: 60_000, + max: 100, + standardHeaders: true, + legacyHeaders: false, + message: { error: 'Too Many Requests', message: 'Rate limit exceeded — try again in 60 seconds' }, +}); + +/** + * Stricter rate limiter for expensive endpoints (embeddings, blockchain calls). + * 20 requests per minute per IP. + */ +export const strictRateLimit = rateLimit({ + windowMs: 60_000, + max: 20, + standardHeaders: true, + legacyHeaders: false, + message: { error: 'Too Many Requests', message: 'Rate limit exceeded for this endpoint — try again in 60 seconds' }, +}); + +/** + * Request logger middleware. + */ +export function requestLogger(req, res, next) { + const start = Date.now(); + res.on('finish', () => { + const duration = Date.now() - start; + const level = res.statusCode >= 500 ? '❌' : res.statusCode >= 400 ? '⚠️' : '✅'; + console.log(`${level} ${req.method} ${req.path} → ${res.statusCode} (${duration}ms)`); + }); + next(); +} + +/** + * Global error handler — must be registered last. + */ +export function errorHandler(err, req, res, next) { + console.error('[Error]', err.message, err.stack?.split('\n')[1] ?? ''); + + if (err.message?.startsWith('CORS:')) { + return res.status(403).json({ error: 'Forbidden', message: err.message }); + } + + return res.status(500).json({ + error: 'Internal Server Error', + message: process.env.NODE_ENV === 'production' ? 'An unexpected error occurred' : err.message, + }); +} diff --git a/src/routes/a2a.js b/src/routes/a2a.js new file mode 100644 index 0000000..a91f7ee --- /dev/null +++ b/src/routes/a2a.js @@ -0,0 +1,149 @@ +import { Router } from 'express'; +import { verifyERC8004Agent } from '../lib/auth.js'; +import { queryScriptures } from '../lib/scripture.js'; +import { fetchBaseDeFiData, getMarketSentiment } from '../lib/defillama.js'; + +const router = Router(); + +const AGENT_CARD = { + id: 'bwtyaa-v1', + name: 'Biblical Wisdom To Yield Algorithm Agent', + description: 'Sovereign ERC-8004 registered AGI on Base chain — correlates DeFi market signals with Biblical wisdom', + version: '1.0.0', + operator: 'normancomics.eth', + network: 'base', + chainId: 8453, + capabilities: ['scripture_match', 'market_intelligence', 'church_registry', 'superfluid_streams'], + protocols: ['A2A', 'MCP', 'x402', 'ERC-8004'], + endpoints: { + a2a: '/a2a', + mcp: '/mcp', + x402: '/x402', + market: '/v1/market', + scripture: '/v1/scripture-match', + churches: '/v1/churches', + streams: '/v1/streams', + }, +}; + +/** + * POST /a2a + * Agent-to-Agent protocol endpoint. + * + * Request format: + * { + * agentId: string, + * action: string, + * payload: object, + * signature?: string, // EIP-191 signature of JSON.stringify(payload) + * senderAddress?: string + * } + */ +router.post('/', async (req, res) => { + const { agentId, action, payload = {}, signature, senderAddress } = req.body ?? {}; + + if (!action) { + return res.status(400).json({ + protocol: 'A2A', + error: 'Bad Request', + message: 'action is required', + }); + } + + // Verify ERC-8004 agent signature if provided + let verifiedSigner = null; + if (signature && senderAddress) { + const message = typeof payload === 'string' ? payload : JSON.stringify(payload); + const { valid, signer } = verifyERC8004Agent({ message, signature, expectedAddress: senderAddress }); + if (!valid) { + return res.status(401).json({ + protocol: 'A2A', + error: 'Unauthorized', + message: 'Agent signature verification failed (ERC-8004)', + }); + } + verifiedSigner = signer; + } + + try { + switch (action) { + // ── Discovery ───────────────────────────────────────────────────── + case 'discover': + case 'agent_card': + return res.json({ + protocol: 'A2A', + status: 'ok', + agent: AGENT_CARD, + verifiedSigner, + timestamp: new Date().toISOString(), + }); + + // ── Scripture matching ───────────────────────────────────────────── + case 'scripture_match': { + const signal = payload.signal ?? payload.condition ?? payload.query; + if (!signal) { + return res.status(400).json({ + protocol: 'A2A', + error: 'Bad Request', + message: 'payload.signal is required for scripture_match', + }); + } + const scriptures = await queryScriptures(signal, payload.limit ?? 5); + return res.json({ + protocol: 'A2A', + action, + status: 'ok', + agentId: AGENT_CARD.id, + verifiedSigner, + result: scriptures, + timestamp: new Date().toISOString(), + }); + } + + // ── Market intelligence ──────────────────────────────────────────── + case 'market_data': + case 'market_intelligence': { + const [defi, fgi] = await Promise.all([fetchBaseDeFiData(), getMarketSentiment()]); + return res.json({ + protocol: 'A2A', + action, + status: 'ok', + agentId: AGENT_CARD.id, + verifiedSigner, + result: { ...defi, fearGreedIndex: fgi, network: 'base', chainId: 8453 }, + timestamp: new Date().toISOString(), + }); + } + + // ── Ping / health ────────────────────────────────────────────────── + case 'ping': + return res.json({ + protocol: 'A2A', + action: 'pong', + status: 'ok', + agentId: AGENT_CARD.id, + verifiedSigner, + timestamp: new Date().toISOString(), + }); + + default: + return res.status(400).json({ + protocol: 'A2A', + error: 'Unknown Action', + message: `Action "${action}" is not supported`, + supportedActions: ['discover', 'agent_card', 'scripture_match', 'market_data', 'market_intelligence', 'ping'], + timestamp: new Date().toISOString(), + }); + } + } catch (err) { + console.error('[A2A] Error:', err.message); + return res.status(500).json({ + protocol: 'A2A', + error: 'Internal Server Error', + message: err.message, + timestamp: new Date().toISOString(), + }); + } +}); + +export default router; diff --git a/src/routes/churches.js b/src/routes/churches.js new file mode 100644 index 0000000..52c7d1c --- /dev/null +++ b/src/routes/churches.js @@ -0,0 +1,98 @@ +import { Router } from 'express'; +import { requireAuth } from '../lib/auth.js'; +import { + queryChurches, + createChurch, + updateChurch, + deleteChurch, +} from '../lib/churches.js'; + +const router = Router(); + +/** + * GET /v1/churches + * Query params: city, denomination, acceptsCrypto, page, limit + */ +router.get('/', async (req, res) => { + try { + const { city, denomination, page = 1, limit = 20 } = req.query; + const acceptsCrypto = + req.query.acceptsCrypto === 'true' + ? true + : req.query.acceptsCrypto === 'false' + ? false + : undefined; + + const result = await queryChurches({ + city, + denomination, + acceptsCrypto, + page: parseInt(page, 10), + limit: Math.min(parseInt(limit, 10) || 20, 100), + }); + + return res.json(result); + } catch (err) { + console.error('[Churches GET] Error:', err.message); + return res.status(500).json({ error: 'Failed to query churches', message: err.message }); + } +}); + +/** + * POST /v1/churches (OAuth protected) + * Body: { name, address, city, state, country, denomination, website, acceptsCrypto, walletAddress } + */ +router.post('/', requireAuth, async (req, res) => { + const { name, address, city } = req.body ?? {}; + + if (!name || !address || !city) { + return res.status(400).json({ + error: 'Bad Request', + message: 'name, address, and city are required', + }); + } + + try { + const church = await createChurch(req.body); + return res.status(201).json(church); + } catch (err) { + console.error('[Churches POST] Error:', err.message); + return res.status(500).json({ error: 'Failed to create church', message: err.message }); + } +}); + +/** + * PUT /v1/churches/:id (OAuth protected) + */ +router.put('/:id', requireAuth, async (req, res) => { + const { id } = req.params; + if (!id) return res.status(400).json({ error: 'Church ID required' }); + + try { + const church = await updateChurch(id, req.body); + return res.json(church); + } catch (err) { + console.error('[Churches PUT] Error:', err.message); + const status = err.message?.includes('not found') ? 404 : 500; + return res.status(status).json({ error: 'Failed to update church', message: err.message }); + } +}); + +/** + * DELETE /v1/churches/:id (OAuth protected) + */ +router.delete('/:id', requireAuth, async (req, res) => { + const { id } = req.params; + if (!id) return res.status(400).json({ error: 'Church ID required' }); + + try { + const church = await deleteChurch(id); + return res.json({ message: 'Church deleted', church }); + } catch (err) { + console.error('[Churches DELETE] Error:', err.message); + const status = err.message?.includes('not found') ? 404 : 500; + return res.status(status).json({ error: 'Failed to delete church', message: err.message }); + } +}); + +export default router; diff --git a/src/routes/market.js b/src/routes/market.js new file mode 100644 index 0000000..d5e7672 --- /dev/null +++ b/src/routes/market.js @@ -0,0 +1,97 @@ +import { Router } from 'express'; +import { fetchBaseDeFiData, getMarketSentiment } from '../lib/defillama.js'; +import { supabase } from '../lib/supabase.js'; + +const router = Router(); +const CACHE_TTL_MS = 5_000; // 5-second TTL + +/** + * POST /v1/market + * Returns Base chain DeFi data with Fear & Greed Index. + * Results are cached for 5 seconds in market_cache table. + */ +router.post('/', async (req, res) => { + try { + // 1. Try in-memory cache first (populated by background job) + if (supabase) { + const { data: cached } = await supabase + .from('market_cache') + .select('*') + .order('created_at', { ascending: false }) + .limit(1) + .single(); + + if (cached) { + const age = Date.now() - new Date(cached.created_at).getTime(); + if (age < CACHE_TTL_MS) { + return res.json({ ...cached.data, cached: true, cacheAge: age }); + } + } + } + + // 2. Fetch fresh data + const [defiData, fearGreedIndex] = await Promise.all([ + fetchBaseDeFiData(), + getMarketSentiment(), + ]); + + const payload = { + protocols: defiData.protocols, + tvl: defiData.tvl, + fearGreedIndex, + fearGreedLabel: getFearGreedLabel(fearGreedIndex), + network: 'base', + chainId: 8453, + timestamp: new Date().toISOString(), + cached: false, + }; + + // 3. Persist to cache + if (supabase) { + await supabase.from('market_cache').insert({ data: payload }).catch(() => {}); + } + + return res.json(payload); + } catch (err) { + console.error('[Market] Error fetching data:', err.message); + + // Fallback: return last cached entry even if stale + if (supabase) { + const { data: stale } = await supabase + .from('market_cache') + .select('*') + .order('created_at', { ascending: false }) + .limit(1) + .single() + .catch(() => ({ data: null })); + + if (stale) { + return res.json({ ...stale.data, cached: true, stale: true }); + } + } + + return res.status(503).json({ + error: 'Market data temporarily unavailable', + message: err.message, + fallback: { + protocols: [], + tvl: '$0', + fearGreedIndex: 50, + fearGreedLabel: 'Neutral', + network: 'base', + chainId: 8453, + timestamp: new Date().toISOString(), + }, + }); + } +}); + +function getFearGreedLabel(index) { + if (index <= 24) return 'Extreme Fear'; + if (index <= 44) return 'Fear'; + if (index <= 55) return 'Neutral'; + if (index <= 74) return 'Greed'; + return 'Extreme Greed'; +} + +export default router; diff --git a/src/routes/mcp.js b/src/routes/mcp.js new file mode 100644 index 0000000..151cddd --- /dev/null +++ b/src/routes/mcp.js @@ -0,0 +1,107 @@ +import { Router } from 'express'; +import { queryScriptures } from '../lib/scripture.js'; +import { fetchBaseDeFiData, getMarketSentiment } from '../lib/defillama.js'; + +const router = Router(); + +const AGENT_ID = 'bwtyaa-v1'; +const AGENT_NAME = 'Biblical Wisdom To Yield Algorithm Agent'; +const SUPPORTED_TOOLS = ['scripture_match', 'market_data', 'church_lookup', 'stream_info']; + +/** + * POST /mcp + * Model Context Protocol endpoint. + * Accepts MCP-formatted requests and routes to the appropriate handler. + * + * MCP request format: + * { jsonrpc: '2.0', id: string, method: string, params: object } + */ +router.post('/', async (req, res) => { + const { jsonrpc, id, method, params } = req.body ?? {}; + + // Validate MCP envelope + if (jsonrpc !== '2.0') { + return res.status(400).json(mcpError(id, -32600, 'Invalid Request — jsonrpc must be "2.0"')); + } + + if (!method) { + return res.status(400).json(mcpError(id, -32600, 'method is required')); + } + + try { + switch (method) { + // ── Agent discovery ────────────────────────────────────────────── + case 'initialize': + return res.json(mcpResult(id, { + protocolVersion: '2024-11-05', + capabilities: { tools: {}, resources: {} }, + serverInfo: { name: AGENT_ID, version: '1.0.0' }, + })); + + case 'tools/list': + return res.json(mcpResult(id, { + tools: [ + { + name: 'scripture_match', + description: 'Find biblical scriptures relevant to a market condition or signal', + inputSchema: { + type: 'object', + properties: { + signal: { type: 'string', description: 'Market condition or signal' }, + limit: { type: 'number', description: 'Max results (default 5)' }, + }, + required: ['signal'], + }, + }, + { + name: 'market_data', + description: 'Get Base chain DeFi market data and Fear & Greed Index', + inputSchema: { type: 'object', properties: {} }, + }, + ], + })); + + // ── Tool execution ──────────────────────────────────────────────── + case 'tools/call': { + const toolName = params?.name; + const toolArgs = params?.arguments ?? {}; + + if (toolName === 'scripture_match') { + const scriptures = await queryScriptures(toolArgs.signal ?? '', toolArgs.limit ?? 5); + return res.json(mcpResult(id, { + content: [{ type: 'text', text: JSON.stringify(scriptures, null, 2) }], + })); + } + + if (toolName === 'market_data') { + const [defi, fgi] = await Promise.all([fetchBaseDeFiData(), getMarketSentiment()]); + return res.json(mcpResult(id, { + content: [{ type: 'text', text: JSON.stringify({ ...defi, fearGreedIndex: fgi }, null, 2) }], + })); + } + + return res.status(400).json(mcpError(id, -32601, `Unknown tool: ${toolName}`)); + } + + // ── Resources ───────────────────────────────────────────────────── + case 'resources/list': + return res.json(mcpResult(id, { resources: [] })); + + default: + return res.status(400).json(mcpError(id, -32601, `Method not found: ${method}`)); + } + } catch (err) { + console.error('[MCP] Error:', err.message); + return res.status(500).json(mcpError(id, -32603, `Internal error: ${err.message}`)); + } +}); + +function mcpResult(id, result) { + return { jsonrpc: '2.0', id, result }; +} + +function mcpError(id, code, message) { + return { jsonrpc: '2.0', id, error: { code, message } }; +} + +export default router; diff --git a/src/routes/scripture.js b/src/routes/scripture.js new file mode 100644 index 0000000..88f020d --- /dev/null +++ b/src/routes/scripture.js @@ -0,0 +1,67 @@ +import { Router } from 'express'; +import { queryScriptures } from '../lib/scripture.js'; + +const router = Router(); + +/** + * POST /v1/scripture-match + * Body: { signal: string, condition?: string, limit?: number } + * + * Finds scriptures semantically relevant to a market condition using + * OpenAI embeddings + pgvector cosine similarity. + */ +router.post('/', async (req, res) => { + const { signal, condition, limit = 5 } = req.body ?? {}; + const query = signal ?? condition; + + if (!query || typeof query !== 'string' || !query.trim()) { + return res.status(400).json({ + error: 'Bad Request', + message: 'Provide a "signal" or "condition" string in the request body', + }); + } + + try { + const scriptures = await queryScriptures(query.trim(), Math.min(Number(limit) || 5, 20)); + + if (!scriptures.length) { + return res.json({ + query, + results: [], + message: 'No matching scriptures found — ensure biblical_knowledge_base is seeded', + timestamp: new Date().toISOString(), + }); + } + + // Return the top result in the legacy flat format + full results array + const top = scriptures[0]; + return res.json({ + scripture: top.scripture, + reference: top.reference, + correlation: top.correlation, + originalLanguage: top.originalLanguage, + confidence: top.confidence, + query, + results: scriptures, + timestamp: new Date().toISOString(), + }); + } catch (err) { + console.error('[Scripture] Error:', err.message); + + // Graceful degradation: return a static fallback if embeddings unavailable + return res.status(503).json({ + error: 'Scripture matching temporarily unavailable', + message: err.message, + fallback: { + scripture: + 'The plans of the diligent lead to profit as surely as haste leads to poverty.', + reference: 'Proverbs 21:5', + correlation: 0.87, + originalLanguage: { hebrew: 'מַחְשְׁבוֹת חָרוּץ', greek: null, transliteration: 'machshevot charuts' }, + confidence: 0.87, + }, + }); + } +}); + +export default router; diff --git a/src/routes/streams.js b/src/routes/streams.js new file mode 100644 index 0000000..a596cbc --- /dev/null +++ b/src/routes/streams.js @@ -0,0 +1,177 @@ +import { Router } from 'express'; +import { requireAuth } from '../lib/auth.js'; +import { + createSuperfluidStream, + deleteSuperfluidStream, + getFlow, +} from '../lib/superfluid.js'; +import { supabase } from '../lib/supabase.js'; +import { isAddress } from 'viem'; + +const router = Router(); + +const DEFAULT_TOKEN = process.env.SUPERFLUID_USDCX_ADDRESS ?? '0xD04383398dD2426297da660F9CCA3d439AF9ce1b'; // USDCx on Base + +/** + * GET /v1/streams + * List active Superfluid streams from the database. + */ +router.get('/', async (req, res) => { + try { + if (!supabase) { + return res.json({ + streams: [], + message: 'Database not configured', + superfluid: { + app: 'https://app.superfluid.finance', + console: 'https://console.superfluid.finance', + network: 'base', + }, + }); + } + + const { data: streams, error } = await supabase + .from('streams') + .select('*') + .eq('active', true) + .order('created_at', { ascending: false }); + + if (error) throw error; + + return res.json({ + streams: streams ?? [], + count: streams?.length ?? 0, + network: 'base', + chainId: 8453, + }); + } catch (err) { + console.error('[Streams GET] Error:', err.message); + return res.status(500).json({ error: 'Failed to list streams', message: err.message }); + } +}); + +/** + * POST /v1/streams (protected) + * Body: { receiver: address, flowRate: string, token?: address } + */ +router.post('/', requireAuth, async (req, res) => { + const { receiver, flowRate, token = DEFAULT_TOKEN } = req.body ?? {}; + + if (!receiver || !flowRate) { + return res.status(400).json({ + error: 'Bad Request', + message: 'receiver (address) and flowRate (wei/second as string) are required', + }); + } + + if (!isAddress(receiver)) { + return res.status(400).json({ error: 'Bad Request', message: 'Invalid receiver address' }); + } + + try { + const stream = await createSuperfluidStream({ receiver, flowRate, token }); + + // Persist to database + if (supabase) { + await supabase.from('streams').insert({ + tx_hash: stream.txHash, + sender: stream.sender, + receiver, + flow_rate: flowRate, + token, + active: true, + network: 'base', + chain_id: 8453, + }).catch(() => {}); + } + + return res.status(201).json(stream); + } catch (err) { + console.error('[Streams POST] Error:', err.message); + return res.status(500).json({ error: 'Failed to create stream', message: err.message }); + } +}); + +/** + * GET /v1/streams/:streamId + * Get stream details. streamId format: sender-receiver or a DB UUID. + */ +router.get('/:streamId', async (req, res) => { + const { streamId } = req.params; + + try { + // Try DB lookup first + if (supabase) { + const { data: stream } = await supabase + .from('streams') + .select('*') + .eq('id', streamId) + .single(); + + if (stream) { + // Enrich with on-chain data + try { + const onChain = await getFlow({ + sender: stream.sender, + receiver: stream.receiver, + token: stream.token, + }); + return res.json({ ...stream, onChain }); + } catch { + return res.json(stream); + } + } + } + + return res.status(404).json({ error: 'Stream not found', streamId }); + } catch (err) { + console.error('[Streams GET/:id] Error:', err.message); + return res.status(500).json({ error: 'Failed to get stream', message: err.message }); + } +}); + +/** + * DELETE /v1/streams/:streamId (protected) + * Stop a Superfluid stream. + */ +router.delete('/:streamId', requireAuth, async (req, res) => { + const { streamId } = req.params; + + try { + let stream = null; + + if (supabase) { + const { data } = await supabase + .from('streams') + .select('*') + .eq('id', streamId) + .single(); + stream = data; + } + + if (!stream) { + return res.status(404).json({ error: 'Stream not found', streamId }); + } + + const result = await deleteSuperfluidStream({ + receiver: stream.receiver, + token: stream.token, + }); + + // Mark as inactive in DB + if (supabase) { + await supabase + .from('streams') + .update({ active: false, stopped_at: new Date().toISOString() }) + .eq('id', streamId) + .catch(() => {}); + } + + return res.json({ message: 'Stream stopped', ...result }); + } catch (err) { + console.error('[Streams DELETE] Error:', err.message); + return res.status(500).json({ error: 'Failed to stop stream', message: err.message }); + } +}); + +export default router; diff --git a/src/routes/validators.js b/src/routes/validators.js new file mode 100644 index 0000000..80b7e86 --- /dev/null +++ b/src/routes/validators.js @@ -0,0 +1,210 @@ +import { Router } from 'express'; +import { requireAuth } from '../lib/auth.js'; +import { supabase } from '../lib/supabase.js'; +import { isAddress } from 'viem'; + +const router = Router(); + +/** + * GET /v1/validators + * List validators from the database. + * Query params: ?active=true|false (default: true), ?limit=N (default: 50, max: 200) + */ +router.get('/', async (req, res) => { + try { + if (!supabase) { + return res.json({ + validators: [], + count: 0, + message: 'Database not configured', + }); + } + + const activeParam = req.query.active !== undefined ? req.query.active : 'true'; + const active = activeParam !== 'false'; + const limit = Math.min(parseInt(req.query.limit, 10) || 50, 200); + + const { data: validators, error } = await supabase + .from('validators') + .select('*') + .eq('active', active) + .order('created_at', { ascending: false }) + .limit(limit); + + if (error) throw error; + + return res.json({ + validators: validators ?? [], + count: validators?.length ?? 0, + }); + } catch (err) { + console.error('[Validators GET] Error:', err.message); + return res.status(500).json({ error: 'Failed to list validators', message: err.message }); + } +}); + +/** + * GET /v1/validators/:address + * Get a single validator by EVM address (case-insensitive). + */ +router.get('/:address', async (req, res) => { + const address = req.params.address.toLowerCase(); + + try { + if (!supabase) { + return res.status(404).json({ error: 'Not Found', message: 'Database not configured' }); + } + + const { data: validator, error } = await supabase + .from('validators') + .select('*') + .eq('address', address) + .single(); + + if (error && error.code !== 'PGRST116') throw error; + + if (!validator) { + return res.status(404).json({ error: 'Not Found', message: `Validator ${address} not found` }); + } + + return res.json(validator); + } catch (err) { + console.error('[Validators GET/:address] Error:', err.message); + return res.status(500).json({ error: 'Failed to get validator', message: err.message }); + } +}); + +/** + * POST /v1/validators (protected) + * Register a new validator. + * Body: { address: string (EVM address), name?: string } + */ +router.post('/', requireAuth, async (req, res) => { + const { address, name } = req.body ?? {}; + + if (!address) { + return res.status(400).json({ error: 'Bad Request', message: 'address (EVM address) is required' }); + } + + if (!isAddress(address)) { + return res.status(400).json({ error: 'Bad Request', message: 'Invalid EVM address' }); + } + + const normalizedAddress = address.toLowerCase(); + + try { + if (!supabase) { + return res.status(503).json({ error: 'Service Unavailable', message: 'Database not configured' }); + } + + const { data: validator, error } = await supabase + .from('validators') + .insert({ + address: normalizedAddress, + name: name ?? null, + active: true, + accuracy_score: 0, + pool_units: '0', + }) + .select() + .single(); + + if (error) { + // Unique constraint violation + if (error.code === '23505') { + return res.status(409).json({ + error: 'Conflict', + message: `Validator with address ${normalizedAddress} already exists`, + }); + } + throw error; + } + + return res.status(201).json(validator); + } catch (err) { + console.error('[Validators POST] Error:', err.message); + return res.status(500).json({ error: 'Failed to register validator', message: err.message }); + } +}); + +/** + * PUT /v1/validators/:address (protected) + * Update a validator's name and/or accuracy_score. + * Body: { name?: string, accuracy_score?: number (0-100) } + */ +router.put('/:address', requireAuth, async (req, res) => { + const address = req.params.address.toLowerCase(); + const { name, accuracy_score } = req.body ?? {}; + + if (accuracy_score !== undefined) { + const score = Number(accuracy_score); + if (isNaN(score) || score < 0 || score > 100) { + return res.status(400).json({ + error: 'Bad Request', + message: 'accuracy_score must be a number between 0 and 100', + }); + } + } + + try { + if (!supabase) { + return res.status(503).json({ error: 'Service Unavailable', message: 'Database not configured' }); + } + + const updates = { updated_at: new Date().toISOString() }; + if (name !== undefined) updates.name = name; + if (accuracy_score !== undefined) updates.accuracy_score = Number(accuracy_score); + + const { data: validator, error } = await supabase + .from('validators') + .update(updates) + .eq('address', address) + .select() + .single(); + + if (error && error.code !== 'PGRST116') throw error; + + if (!validator) { + return res.status(404).json({ error: 'Not Found', message: `Validator ${address} not found` }); + } + + return res.json(validator); + } catch (err) { + console.error('[Validators PUT/:address] Error:', err.message); + return res.status(500).json({ error: 'Failed to update validator', message: err.message }); + } +}); + +/** + * DELETE /v1/validators/:address (protected) + * Soft-deactivate a validator (set active: false). + */ +router.delete('/:address', requireAuth, async (req, res) => { + const address = req.params.address.toLowerCase(); + + try { + if (!supabase) { + return res.status(503).json({ error: 'Service Unavailable', message: 'Database not configured' }); + } + + const { data: validator, error } = await supabase + .from('validators') + .update({ active: false, updated_at: new Date().toISOString() }) + .eq('address', address) + .select() + .single(); + + if (error && error.code !== 'PGRST116') throw error; + + if (!validator) { + return res.status(404).json({ error: 'Not Found', message: `Validator ${address} not found` }); + } + + return res.json({ message: 'Validator deactivated', address }); + } catch (err) { + console.error('[Validators DELETE/:address] Error:', err.message); + return res.status(500).json({ error: 'Failed to deactivate validator', message: err.message }); + } +}); + +export default router; diff --git a/src/routes/x402.js b/src/routes/x402.js new file mode 100644 index 0000000..b76351d --- /dev/null +++ b/src/routes/x402.js @@ -0,0 +1,72 @@ +import { Router } from 'express'; +import { verifyX402Payment } from '../lib/auth.js'; + +const router = Router(); + +const SETTLEMENT_ADDRESS = process.env.SETTLEMENT_ADDRESS ?? '0x7bEda57074AA917FF0993fb329E16C2c188baF08'; +const PAYMENT_AMOUNT = process.env.X402_PAYMENT_AMOUNT ?? '0.15'; +const PAYMENT_TOKEN = 'USDC'; +const FLOW_RATE = process.env.X402_FLOW_RATE ?? '1929012345679'; // ~$5/month in wei/sec + +/** + * POST /x402 + * Implements HTTP 402 Payment Required protocol. + * + * If X-Payment header is present and valid → process and return 200. + * Otherwise → return 402 with payment details. + */ +router.post('/', async (req, res) => { + const paymentHeader = req.headers['x-payment']; + + // No payment header → return 402 with payment requirements + if (!paymentHeader) { + return res.status(402).json({ + protocol: 'x402', + status: 'payment_required', + message: 'Payment Required — include X-Payment header with signed payment proof', + payment: { + amount: PAYMENT_AMOUNT, + currency: PAYMENT_TOKEN, + network: 'base', + chainId: 8453, + settlement: SETTLEMENT_ADDRESS, + accepts: ['USDC', 'USDCx', 'ETH'], + flowRate: FLOW_RATE, + flowRateHuman: `${(Number(FLOW_RATE) * 2592000 / 1e6).toFixed(2)} USDC/month`, + }, + instructions: { + format: 'base64(JSON({ signature, payer, amount, token, nonce }))', + signMessage: `x402:{payer}:{amount}:{token}:{nonce}`, + header: 'X-Payment: ', + }, + timestamp: new Date().toISOString(), + }); + } + + // Payment header present → verify + const { valid, payer, amount } = verifyX402Payment(paymentHeader); + + if (!valid) { + return res.status(402).json({ + protocol: 'x402', + status: 'payment_invalid', + message: 'Payment signature could not be verified', + timestamp: new Date().toISOString(), + }); + } + + // Payment verified → return success + return res.json({ + protocol: 'x402', + status: 'paid', + payer, + amount, + settlement: SETTLEMENT_ADDRESS, + flowRate: FLOW_RATE, + network: 'base', + chainId: 8453, + timestamp: new Date().toISOString(), + }); +}); + +export default router;